diff --git a/.gitignore b/.gitignore index 13ffb55fec58..52428d7c9c61 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,7 @@ /apps/*/bin /apps/HelloMatlab/blurred.png /apps/HelloMatlab/iir_blur.mex -bazel-bin -bazel-genfiles -bazel-Halide -bazel-out -bazel-testlogs +bazel-* bin/* build-64/* build-ios/* diff --git a/.travis.yml b/.travis.yml index 9c1bd3362c93..819b83b1cbdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,18 +4,22 @@ compiler: #- clang - gcc env: - # Configurations - # - # Each line in the ``env`` section represents a set of environment - # variables passed to a build configuration - # - # Test a mix of llvm versions and a mix of build systems. - # Note that the Make build ignores the HALIDE_SHARED_LIBRARY option. - # Don't build as a static library with cmake. It risks exceeding the travis memory limit. - - LLVM_VERSION=3.7.1 BUILD_SYSTEM=MAKE CXX_=g++-4.8 CC_=gcc-4.8 - - LLVM_VERSION=3.8.1 BUILD_SYSTEM=MAKE CXX_=g++-4.8 CC_=gcc-4.8 - - LLVM_VERSION=3.8.1 BUILD_SYSTEM=CMAKE CXX_=g++-4.8 CC_=gcc-4.8 HALIDE_SHARED_LIBRARY=1 + global: + - BAZEL_VERSION="0.5.2" + - JDK=openjdk8 + matrix: + # Configurations + # + # Each line in the ``env`` section represents a set of environment + # variables passed to a build configuration + # + # Test a mix of llvm versions, a mix of build systems, and a mix of shared vs static library + # Don't build as a static library with cmake. It risks exceeding the travis memory limit. + - LLVM_VERSION=3.7.1 BUILD_SYSTEM=MAKE CXX_=g++-4.8 CC_=gcc-4.8 + - LLVM_VERSION=3.8.1 BUILD_SYSTEM=MAKE CXX_=g++-4.8 CC_=gcc-4.8 + - LLVM_VERSION=3.8.1 BUILD_SYSTEM=CMAKE CXX_=g++-4.8 CC_=gcc-4.8 HALIDE_SHARED_LIBRARY=1 cache: apt +dist: trusty # Note the commands below are written assuming Ubuntu 12.04LTS before_install: # Needed for new libstdc++ and gcc4.8 @@ -23,6 +27,8 @@ before_install: - export CC=${CC_} - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test/ - sudo apt-get update + - wget https://github.com/bazelbuild/bazel/releases/download/"${BAZEL_VERSION}"/bazel_"${BAZEL_VERSION}"-linux-x86_64.deb + - sudo dpkg -i bazel_"${BAZEL_VERSION}"-linux-x86_64.deb install: - sudo apt-get -y --force-yes install ${CXX} ${CC} libedit-dev # Make gcc4.8 the default gcc version diff --git a/Makefile b/Makefile index 922ed8a773b1..ffd90a836fb0 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,8 @@ else endif endif +BAZEL ?= $(shell which bazel) + SHELL = bash CXX ?= g++ PREFIX ?= /usr/local @@ -1425,6 +1427,24 @@ test_apps: $(LIB_DIR)/libHalide.a $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_D make -C apps/linear_algebra test HALIDE_BIN_PATH=$(CURDIR) HALIDE_SRC_PATH=$(ROOT_DIR) ; \ fi +# Bazel depends on the distrib archive being built +.PHONY: test_bazel +test_bazel: $(DISTRIB_DIR)/halide.tgz + # Only test bazeldemo if Bazel is installed + if [ -z "$(BAZEL)" ]; then echo "Bazel is not installed"; exit 1; fi + mkdir -p apps + # Make a local copy of the apps if we're building out-of-tree, + # because the app Makefiles are written to build in-tree + if [ "$(ROOT_DIR)" != "$(CURDIR)" ]; then \ + echo "Building out-of-tree, so making local copy of apps"; \ + cp -r $(ROOT_DIR)/apps/bazeldemo apps; \ + cp -r $(ROOT_DIR)/tools .; \ + fi + cd apps/bazeldemo; \ + CXX=`echo ${CXX} | sed 's/ccache //'` \ + CC=`echo ${CC} | sed 's/ccache //'` \ + bazel build --verbose_failures :all + .PHONY: test_python test_python: $(LIB_DIR)/libHalide.a $(INCLUDE_DIR)/Halide.h mkdir -p python_bindings @@ -1524,7 +1544,11 @@ ifeq ($(UNAME), Darwin) install_name_tool -id $(PREFIX)/lib/libHalide.$(SHARED_EXT) $(PREFIX)/lib/libHalide.$(SHARED_EXT) endif -$(DISTRIB_DIR)/halide.tgz: $(LIB_DIR)/libHalide.a $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_DIR)/Halide.h $(RUNTIME_EXPORTED_INCLUDES) +$(BUILD_DIR)/halide_config.bzl: $(ROOT_DIR)/bazel/create_halide_config.sh + -mkdir -p $(BUILD_DIR) + $< > $@ + +$(DISTRIB_DIR)/halide.tgz: $(LIB_DIR)/libHalide.a $(BIN_DIR)/libHalide.$(SHARED_EXT) $(INCLUDE_DIR)/Halide.h $(RUNTIME_EXPORTED_INCLUDES) $(ROOT_DIR)/bazel/* $(BUILD_DIR)/halide_config.bzl mkdir -p $(DISTRIB_DIR)/include $(DISTRIB_DIR)/bin $(DISTRIB_DIR)/lib $(DISTRIB_DIR)/tutorial $(DISTRIB_DIR)/tutorial/images $(DISTRIB_DIR)/tools $(DISTRIB_DIR)/tutorial/figures cp $(BIN_DIR)/libHalide.$(SHARED_EXT) $(DISTRIB_DIR)/bin cp $(LIB_DIR)/libHalide.a $(DISTRIB_DIR)/lib @@ -1546,8 +1570,27 @@ $(DISTRIB_DIR)/halide.tgz: $(LIB_DIR)/libHalide.a $(BIN_DIR)/libHalide.$(SHARED_ cp $(ROOT_DIR)/tools/halide_image_io.h $(DISTRIB_DIR)/tools cp $(ROOT_DIR)/tools/halide_image_info.h $(DISTRIB_DIR)/tools cp $(ROOT_DIR)/README.md $(DISTRIB_DIR) + cp $(ROOT_DIR)/bazel/BUILD $(DISTRIB_DIR) + cp $(ROOT_DIR)/bazel/halide.bzl $(DISTRIB_DIR) + cp $(ROOT_DIR)/bazel/README_bazel.md $(DISTRIB_DIR) + cp $(ROOT_DIR)/bazel/WORKSPACE $(DISTRIB_DIR) + cp $(BUILD_DIR)/halide_config.bzl $(DISTRIB_DIR) ln -sf $(DISTRIB_DIR) halide - tar -czf $(DISTRIB_DIR)/halide.tgz halide/bin halide/lib halide/include halide/tutorial halide/README.md halide/tools/mex_halide.m halide/tools/*.cpp halide/tools/halide_image.h halide/tools/halide_image_io.h halide/tools/halide_image_info.h + tar -czf $(DISTRIB_DIR)/halide.tgz \ + halide/bin \ + halide/lib \ + halide/include \ + halide/tutorial \ + halide/BUILD \ + halide/README.md \ + halide/README_bazel.md \ + halide/WORKSPACE \ + halide/*.bzl \ + halide/tools/mex_halide.m \ + halide/tools/*.cpp \ + halide/tools/halide_image.h \ + halide/tools/halide_image_io.h \ + halide/tools/halide_image_info.h rm -rf halide .PHONY: distrib diff --git a/apps/bazeldemo/BUILD b/apps/bazeldemo/BUILD new file mode 100644 index 000000000000..692ba5bfa8c9 --- /dev/null +++ b/apps/bazeldemo/BUILD @@ -0,0 +1,15 @@ +load("@halide//:halide.bzl", "halide_library") + +halide_library( + name="bazeldemo", + srcs=["bazeldemo_generator.cpp"] +) + +cc_binary( + name = "main", + srcs = ["main.cpp"], + deps = [ + ":bazeldemo", + "@halide//:halide_buffer" + ], +) diff --git a/apps/bazeldemo/WORKSPACE b/apps/bazeldemo/WORKSPACE new file mode 100644 index 000000000000..4bd29aca5e52 --- /dev/null +++ b/apps/bazeldemo/WORKSPACE @@ -0,0 +1,5 @@ +# Note that this requires you to have built the toplevel Halide 'distrib' folder via 'make distrib' +local_repository( + name = "halide", + path = "../../distrib", +) diff --git a/apps/bazeldemo/bazeldemo_generator.cpp b/apps/bazeldemo/bazeldemo_generator.cpp new file mode 100644 index 000000000000..3fd05fc00fab --- /dev/null +++ b/apps/bazeldemo/bazeldemo_generator.cpp @@ -0,0 +1,33 @@ +#include "Halide.h" + +namespace { + +class BazelDemo : public Halide::Generator { + public: + GeneratorParam vectorize{"vectorize", true}; + GeneratorParam parallelize{"parallelize", true}; + + Input> input{"input", 2}; + Input scale{"scale"}; + + Output> output{"output", 2}; + + void generate() { + output(x, y) = input(x, y) * scale; + } + void schedule() { + if (vectorize) { + output.vectorize(x, natural_vector_size()); + } + if (parallelize) { + output.parallel(y); + } + } + + private: + Var x, y; +}; + +HALIDE_REGISTER_GENERATOR(BazelDemo, "bazeldemo") + +} // namespace diff --git a/apps/bazeldemo/main.cpp b/apps/bazeldemo/main.cpp new file mode 100644 index 000000000000..bd8600a4f8d4 --- /dev/null +++ b/apps/bazeldemo/main.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include "HalideBuffer.h" +#include "bazeldemo.h" // Generated by Bazel via halide_library() rule + +int main(int argc, char **argv) { + constexpr int kEdge = 30; + constexpr float kMax = kEdge * kEdge; + + Halide::Runtime::Buffer input(kEdge, kEdge); + for (int x = 0; x < kEdge; ++x) { + for (int y = 0; y < kEdge; ++y) { + input(x, y) = static_cast(x + y) / kMax; + } + } + + const float kScale = 0.5f; + Halide::Runtime::Buffer output(kEdge, kEdge); + int result = bazeldemo(input, kScale, output); + if (result != 0) { + fprintf(stderr, "Failure: %d\n", result); + return -1; + } + + for (int x = 0; x < kEdge; ++x) { + for (int y = 0; y < kEdge; ++y) { + const float expected = input(x, y) * kScale; + constexpr float kEpsilon = 0.00001f; + if (fabs(expected - output(x, y)) > kEpsilon) { + fprintf(stderr, "Expected %f, Got %f\n", expected, output(x, y)); + return -1; + } + } + } + + printf("Success!\n"); + return 0; +} diff --git a/bazel/BUILD b/bazel/BUILD new file mode 100644 index 000000000000..400b13d97a7f --- /dev/null +++ b/bazel/BUILD @@ -0,0 +1,87 @@ +# Bazel build rules for clients using Halize distributions. +# Note that these rules aren't meant to build Halide itself; +# they assume that a Halide library has already been built, +# and that a downstream client wants to use it. +# +# These rules have only been tested with Bazel 0.5+, and are unlikely +# to work with earlier versions of Bazel. + +package( + default_visibility = ["//visibility:public"], +) + +load( + "@halide//:halide.bzl", + "halide_config_settings", + "halide_language_copts", + "halide_library_runtimes", +) + +halide_config_settings() + +halide_library_runtimes() + +cc_library( + name = "language", + hdrs = ["include/Halide.h"], + copts = halide_language_copts(), + includes = ["include"], + deps = [ + ":lib_halide_static", + ":runtime", + ], +) + +# 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 = "runtime", + hdrs = glob(["include/HalideRuntime*.h"]), + includes = ["include"], +) + +# Header-only library to let clients to use Halide::Buffer at runtime. +# (Generators should never need to use this library.) +cc_library( + name = "halide_buffer", + hdrs = glob(["include/HalideBuffer*.h"]), + includes = ["include"], +) + +# Config setting to catch the case where someone is trying to build +# on Windows, but forgot to specify --host_cpu=x64_windows_msvc AND +# --cpu=x64_windows_msvc. +config_setting( + name = "windows_not_using_msvc", + values = {"cpu": "x64_windows"}, +) + +cc_library( + name = "lib_halide_static", + srcs = select({ + ":windows_not_using_msvc": [ + "please_set_host_cpu_and_cpu_to_x86_64_windows", + ], + "@halide//:halide_config_x86_64_windows": [ + "Release/Halide.lib", + "Release/Halide.dll", + ], + "//conditions:default": [ + "lib/libHalide.a", + ], + }), + visibility = ["//visibility:private"], +) + +# 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 = ["tools/GenGen.cpp"], + includes = ["include"], + deps = [":language"], +) diff --git a/bazel/README_bazel.md b/bazel/README_bazel.md new file mode 100644 index 000000000000..7ed644551c34 --- /dev/null +++ b/bazel/README_bazel.md @@ -0,0 +1,305 @@ +# Bazel Build Rules for Halide + +## Release Notes + +These build rules are known to work well for Linux and OSX systems. Windows +support is still a work-in-progress, and may require tweaking for now. These +rules are intended to replace the experimental rules implementation provided +by https://github.com/halide/halide_bazel, which are now considered +deprecated. + +Bazel 0.5 or later is required. + +## Overview + +The primary support for Halide in Bazel is the `halide_library` rule, which +allows you to build a Generator into an executable for various architectures. + +Halide code is produced in a two-stage build that may seem a bit unusual at +first, so before documenting the build rules, let's 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 an executable +(often referred to as a *Filter*) that efficiently runs the Halide pipeline. + +## Halide Compilation Process + +The Halide compilation process is actually several steps: + +1. Your Generator is compiled using a standard C++ compiler. +2. It's linked with libHalide, LLVM, and a simple command-line wrapper. + This results in an executable known as the *Generator Executable*. +3. The Generator Executable is run *as a tool, at Bazel build time*; this + produces a .a, .h, and a few other Halide-specific files. +4. The resulting .a and .h are bundled into a cc_library() rule. + +The `halide_library` rule (mostly) hides the details of this, with some +unavoidable warts, the main one being that there are two distinct `deps` +possibilities: + +1. Generator deps: Bazel deps that are necessary to compile and/or link the + Generator itself. +2. Filter deps: Bazel deps that are necessary to link and/or run the Filter. + (Generally speaking, you only need Filter deps if you use Halide's + `define_extern` directive.) + +## Using the Halide Build Rules + +The easiest way to use Bazel rules for Halide is via a prebuilt Halide +distribution that includes them. In your Bazel WORKSPACE file, just add an +`http_archive` directive that points to the release you want, e.g., + + http_archive( + name = "halide", + urls = ["https://github.com/halide/Halide/releases/download/release_2049_01_01/halide-linux-64-gcc48-trunk-0123456789abcdef0123456789abcdef01234567.tgz"], + sha256 = "sha256-of-the-release-goes-here" + strip_prefix = "halide", + ) + +If you want to work with a local copy of Halide, you can just use a `local_repository` +rule instead: + + # This assumes you have built 'make distrib' locally, of course + local_repository( + name = "halide", + path = "/path/to/Halide/distrib", + ) + +Then, in your own project's BUILD files, just add + + load("@halide//:halide.bzl", "halide_library") + +and you're good to go. + +## Build Rules + +``` +halide_library(name, srcs, copts, debug_codegen_level, extra_outputs + filter_deps, function_name, generator_args, generator_deps, generator_name, + halide_target_features, halide_target_map, includes, namespace, visibility) +``` + +* **name** *(Name; required)* The name of the build rule. +* **srcs** *(List of labels; required)* source file(s) to compile into the + Generator Executable. +* **copts** *(List of strings; optional)* Options to be passed to the C++ + compiler when building the Generator. +* **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. +* **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) + +* **filter_deps** *(List of labels; optional)* optional list of dependencies + needed by the Filter. + +* **function_name** *(String; optional)* The name of the generated C function + for the filter. If omitted, defaults to the Bazel rule name. + +* **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. + +* **generator_deps** *(List of labels; optional)* optional list of + dependencies needed by the Generator. (These dependencies are *not* included + in the filter produced by `halide_library`, nor in any executable that + depends on it.) + +* **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), assumed to be the same + as the Bazel rule name. + +* **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` in `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) + +* **halide_target_map** *(Dict of List-of-strings; optional)* This allows you + to selectively (or completely) override the default Halide Targets chosen + for each basic architecture by the rule. Each key is a Target with only + arch-os-bits; each value is an ordered list of Targets with (possibly) + additional interesting features, in the order desired. For example, the + default value for this argument might contain: + + halide_target_map = { + "x86-64-linux": [ + "x86-64-linux-sse41-avx-avx2", + "x86-64-linux-sse41-avx", + "x86-64-linux-sse41", + "x86-64-linux", + ], + "x86-32-linux": [ ... ] + "arm-32-android": [ ... ] + # etc + } + + This means "When the build target is any variant of x86-64 for Linux, + generate specializations for AVX2, AVX, SSE41, and none-of-the-above; at + runtime, check *in that order* until finding one that can be safely executed + on the current processor." + + We can use this facility to add extra specializations, e.g.: + + halide_target_map = { + "x86-64-linux": [ + # Specialize for f16c and try it before anything else + "x86-64-linux-sse41-avx-avx2-f16c", + "x86-64-linux-sse41-avx-avx2", + "x86-64-linux-sse41-avx", + "x86-64-linux-sse41", + "x86-64-linux", + ], + } + + Alternately, we can use it to specialize output when we know the target + hardware specifically, e.g.: + + halide_target_map = { + "x86-32-android": [ + # This filter is only intended for a specific Android device, + # which we happen to know supports SSE4.1 but nothing more. + "x86-32-android-sse41" + ], + } + + Note that any features specified in the `halide_target_features` will be + added to *every* target in `halide_target_map`, so specifying + + halide_target_features = [ "debug" ], + halide_target_map = { + "x86-64-linux": [ + "x86-64-linux-sse41" + ], + } + + would actually produce code using an effective target of + `x86-64-linux-sse41-debug`. + +* **includes** *(List of strings; optional)* The list of paths to search + include files when building the Generator. + +* **namespace** *(String; optional)* Namespace of the generated function. If + specified, will create a C++ function that forwards to a C function that is + generated by prefixing it with the namespace name replacing "::" with "_". + +* **visibility** *(List of labels; optional)* Bazel visibility of result. + +`halide_library` returns the fully-qualified Bazel build target, so it can be +used in list comprehensions in BUILD files, e.g.: + + # filters will be ["//my/pkg:filter0", "//my/pkg:filter1", ...] + filters = [halide_library(name="filter%d" % variant, ...) for variant in [0, 1, 2, 3]] + +## Build Rules (Advanced) + +Most code can use the `halide_library` rule directly, but for some usage, it can +be desirable to directly use two lower-level rules. + +``` +halide_generator(name, srcs, copts, deps, generator_name, includes, tags, visibility) +``` + +The `halide_generator` rule compiles Generator source code into an executable; +it corresponds to steps 1 and 2 in the [Halide Compilation +Process](#halide-compilation-process). + +* **name** *(Name; required)* The name of the build rule. + +* **srcs** *(List of labels; required)* source file(s) to compile into the + Generator Executable. + +* **copts** *(List of strings; optional)* Options to be passed to the C++ + compiler when building the Generator. + +* **deps** *(List of labels; optional)* optional list of dependencies needed + by the Generator. + +* **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), assumed to be the same + as the Bazel rule name. + +* **includes** *(List of strings; optional)* The list of paths to search + include files when building the Generator. + +* **tags** *(List of strings; optional)* List of arbitrary text tags. Tags may + be any valid string; default is the empty list. + +* **visibility** *(List of labels; optional)* Bazel visibility of result. + +``` +halide_library_from_generator(name, generator, debug_codegen_level, + deps, extra_outputs, function_name, generator_args, halide_target_features, + halide_target_map, namespace, tags, visibility) +``` + +The `halide_library_from_generator` rule executes a compiled Generator to +produce final object code and related files; it corresponds to steps 3 and 4 in +the [Halide Compilation Process](#halide-compilation-process). + +* **name** *(Name; required)* The name of the build rule. + +* **generator** *(Label; required)* The Bazel label for a `halide_generator` + rule. + +* **debug_codegen_level** *(Integer; optional)* Value t∏ use for + HL_DEBUG_CODEGEN when building the Generator; usually useful only for + advanced Halide debugging. Defaults to zero. + +* **deps** *(List of labels; optional)* optional list of dependencies needed + by the Filter. + +* **extra_outputs** *(List of strings; optional)* A list of extra Halide + outputs to generate at build time. + +* **function_name** *(String; optional)* The name of the generated C function + for the filter. If omitted, defaults to the Bazel rule name. + +* **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. + +* **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` in `src/Target.cpp`. + +* **halide_target_map** *(Dict of List-of-strings; optional)* This allows you + to selectively (or completely) override the default Halide Targets chosen + for each basic architecture by the rule. Each key is a Target with only + arch-os-bits; each value is an ordered list of Targets with (possibly) + additional interesting features, in the order desired. + +* **namespace** *(String; optional)* Namespace of the generated function. If + specified, will create a C++ function that forwards to a C function that is + generated by prefixing it with the namespace name replacing "::" with "_". + +* **tags** *(List of strings; optional)* List of arbitrary text tags. Tags may + be any valid string; default is the empty list. + +* **visibility** *(List of labels; optional)* Bazel visibility of result. + +`halide_library_from_generator` returns the fully-qualified Bazel build target, +so it can be used in list comprehensions in BUILD files. + +## Multi-Architecture Support + +In some cases, it's desirable to have Halide generate multiple variants of the +same Filter, and select the most apppropriate one at runtime; the most common of +these is for x86-64, where you'd like to take advantage of AVX/AVX2 when +possible, falling back to SSE4.1 or SSE2 if necessary. Most x86 targets +have reasonable defaults, but this behavior can be customized as described +under the `halide_target_map` sections, above. diff --git a/bazel/WORKSPACE b/bazel/WORKSPACE new file mode 100644 index 000000000000..c82087e77a6c --- /dev/null +++ b/bazel/WORKSPACE @@ -0,0 +1 @@ +# Placeholder WORKSPACE diff --git a/bazel/create_halide_config.sh b/bazel/create_halide_config.sh new file mode 100755 index 000000000000..5e100b003d45 --- /dev/null +++ b/bazel/create_halide_config.sh @@ -0,0 +1,15 @@ +#!/bin/bash -x +set -e + +# We need to capture the system libraries that we'll need to link +# against, so that downstream consumers of our Bazel rules don't +# have to guess what's necessary on their system; call +# llvm-config and capture the result in a Skylark macro that +# we include in our distribution. +LLVM_SYSTEM_LIBS=`${LLVM_CONFIG} --system-libs | sed -e 's/[\/&]/\\&/g'` + +cat < 1: + return [("halide_config_%s_%s_%s_%s" % (halide_arch, halide_bits, halide_os, cpu), cpu) for cpu in cpus] + else: + return [("halide_config_%s_%s_%s" % (halide_arch, halide_bits, halide_os), cpu) for cpu in cpus] + + + +def _config_settings(halide_target): + return ["@halide//:%s" % s for (s, _) in _config_setting_names_and_cpus(halide_target)] + + +_output_extensions = { + "static_library": ("a", False), + "o": ("o", False), + "h": ("h", False), + "cpp_stub": ("stub.h", False), + "assembly": ("s.txt", True), + "bitcode": ("bc", True), + "stmt": ("stmt", True), + "html": ("html", True), + "cpp": ("cpp", True), +} + + +def _gengen_outputs(filename, halide_target, outputs): + new_outputs = {} + for o in outputs: + if o not in _output_extensions: + fail("Unknown output: " + o) + ext, is_multiple = _output_extensions[o] + if is_multiple and len(halide_target) > 1: + # Special handling needed for ".s.txt" and similar: the suffix from the + # is_multiple case always goes before the final . + # (i.e. "filename.s_suffix.txt", not "filename_suffix.s.txt") + # -- this is awkward, but is what Halide does, so we must match it. + pieces = ext.rsplit(".", 1) + extra = (".%s" % pieces[0]) if len(pieces) > 1 else "" + ext = pieces[-1] + for h in halide_target: + new_outputs[o + h] = "%s%s_%s.%s" % ( + filename, extra, _canonicalize_target(h).replace("-", "_"), ext) + else: + new_outputs[o] = "%s.%s" % (filename, ext) + return new_outputs + + +def _gengen_impl(ctx): + if _has_dupes(ctx.attr.outputs): + fail("Duplicate values in outputs: " + str(ctx.attr.outputs)) + + if not ctx.attr.generator_closure.generator_name: + fail("generator_name must be specified") + + remaps = [".s=.s.txt"] + halide_target = ctx.attr.halide_target + if "windows" in halide_target[-1] and not "mingw" in halide_target[-1]: + remaps += [".obj=.o", ".lib=.a"] + if ctx.attr.sanitizer: + halide_target = [] + for t in ctx.attr.halide_target: + ct = _canonicalize_target("%s-%s" % (t, ctx.attr.sanitizer)) + halide_target += [ct] + remaps += ["%s=%s" % (ct.replace("-", "_"), t.replace("-", "_"))] + + outputs = [ + ctx.new_file(f) + for f in _gengen_outputs( + ctx.attr.filename, + ctx.attr.halide_target, # *not* halide_target + ctx.attr.outputs).values() + ] + + arguments = ["-o", outputs[0].dirname] + if ctx.attr.generate_runtime: + arguments += ["-r", ctx.attr.filename] + if len(halide_target) > 1: + fail("Only one halide_target allowed here") + if ctx.attr.halide_function_name: + fail("halide_function_name not allowed here") + else: + arguments += ["-g", ctx.attr.generator_closure.generator_name] + if ctx.attr.filename: + arguments += ["-n", ctx.attr.filename] + if ctx.attr.halide_function_name: + arguments += ["-f", ctx.attr.halide_function_name] + + if ctx.attr.outputs: + arguments += ["-e", ",".join(ctx.attr.outputs)] + arguments += ["-x", ",".join(remaps)] + arguments += ["target=%s" % ",".join(halide_target)] + if ctx.attr.halide_generator_args: + arguments += ctx.attr.halide_generator_args.split(" ") + + if ctx.executable.hexagon_code_signer: + additional_inputs, _, input_manifests = ctx.resolve_command( + tools=[ctx.attr.hexagon_code_signer]) + hexagon_code_signer = ctx.executable.hexagon_code_signer.path + else: + additional_inputs = [] + input_manifests = None + hexagon_code_signer = "" + + env = { + "HL_DEBUG_CODEGEN": str(ctx.attr.debug_codegen_level), + "HL_HEXAGON_CODE_SIGNER": hexagon_code_signer, + } + ctx.action( + # If you need to force the tools to run locally (e.g. for experimentation), + # uncomment this line. + # execution_requirements={"local":"1"}, + arguments=arguments, + env=env, + executable=ctx.attr.generator_closure.generator_binary.files_to_run.executable, + mnemonic="ExecuteHalideGenerator", + input_manifests=input_manifests, + inputs=additional_inputs, + outputs=outputs, + progress_message="Executing generator %s with target (%s) args (%s)..." % + (ctx.attr.generator_closure.generator_name, + ",".join(halide_target), + ctx.attr.halide_generator_args) + ) + + +_gengen = rule( + implementation=_gengen_impl, + attrs={ + "debug_codegen_level": + attr.int(), + "filename": + attr.string(), + "generate_runtime": + attr.bool(default=False), + "generator_closure": + attr.label( + cfg="host", providers=["generator_binary", "generator_name"]), + "halide_target": + attr.string_list(), + "halide_function_name": + attr.string(), + "halide_generator_args": + attr.string(), + "hexagon_code_signer": + attr.label( + executable=True, cfg="host"), + "outputs": + attr.string_list(), + "sanitizer": + attr.string(), + }, + outputs=_gengen_outputs, + output_to_genfiles=True) + + +def _add_target_features(target, features): + if "," in target: + fail("Cannot use multitarget here") + new_target = target.split("-") + for f in features: + if f and f not in new_target: + new_target += [f] + return "-".join(new_target) + + +def _has_dupes(some_list): + clean = list(set(some_list)) + return sorted(some_list) != sorted(clean) + + +def _select_multitarget(base_target, + halide_target_features, + halide_target_map): + if base_target == "armeabi-32-android": + base_target = "arm-32-android" + wildcard_target = halide_target_map.get("*") + if wildcard_target: + expected_base = "*" + targets = wildcard_target + else: + expected_base = base_target + targets = halide_target_map.get(base_target, [base_target]) + + multitarget = [] + for t in targets: + if not t.startswith(expected_base): + fail( + "target %s does not start with expected target %s for halide_target_map" + % (t, expected_base)) + t = t[len(expected_base):] + if t.startswith("-"): + t = t[1:] + # Check for a "match all base targets" entry: + multitarget.append(_add_target_features(base_target, t.split("-"))) + + # Add the extra features (if any). + if halide_target_features: + multitarget = [ + _add_target_features(t, halide_target_features) for t in multitarget + ] + + # Finally, canonicalize all targets + multitarget = [_canonicalize_target(t) for t in multitarget] + return multitarget + + +def _gengen_closure_impl(ctx): + return struct( + generator_binary=ctx.attr.generator_binary, + generator_name=ctx.attr.halide_generator_name) + + +_gengen_closure = rule( + implementation=_gengen_closure_impl, + attrs={ + "generator_binary": + attr.label( + executable=True, allow_files=True, mandatory=True, cfg="host"), + "halide_generator_name": + attr.string(), + }) + +def _discard_useless_features(halide_target_features = []): + # Discard target features which do not affect the contents of the runtime. + useless_features = set(["user_context", "no_asserts", "no_bounds_query", "profile"]) + return sorted(list(set([f for f in halide_target_features if f not in useless_features]))) + +def _halide_library_runtime_target_name(halide_target_features = []): + return "_".join(["halide_library_runtime"] + _discard_useless_features(halide_target_features)) + +def _define_halide_library_runtime(halide_target_features = []): + target_name = _halide_library_runtime_target_name(halide_target_features) + + if not native.existing_rule("halide_library_runtime_generator"): + halide_generator( + name="halide_library_runtime_generator", + srcs=[], + deps=[], + visibility=["//visibility:private"]) + condition_deps = {} + for base_target, _, _, _ in _HALIDE_TARGET_CONFIG_INFO: + settings = _config_settings(base_target) + if base_target == "armeabi-32-android": + # For armeabi-32-android, just generate an arm-32-android runtime + halide_target = "arm-32-android" + else: + halide_target = base_target + sub_name = (target_name + "_" + + _halide_target_to_bazel_rule_name(base_target)) + _gengen( + name=sub_name, + filename=sub_name, + generate_runtime=True, + generator_closure="halide_library_runtime_generator_closure", + halide_target=["-".join([halide_target] + _discard_useless_features(halide_target_features))], + sanitizer=select({ + "@halide//:halide_config_msan": "msan", + "//conditions:default": "", + }), + outputs=["o"], + tags=["manual"], + visibility=[ + "@halide//:__subpackages__", + ] + ) + for s in settings: + condition_deps[s] = [":%s.o" % sub_name] + + native.cc_library( + name=target_name, + linkopts=halide_runtime_linkopts(), + srcs=select(condition_deps), + tags=["manual"], + visibility=["//visibility:public"]) + + return target_name + +def _standard_library_runtime_features(): + standard_features = [ + [], + ["c_plus_plus_name_mangling"], + ["cuda"], + ["cuda", "matlab"], + ["hvx_64"], + ["hvx_128"], + ["matlab"], + ["metal"], + ["msan"], + ["opengl"], + ] + return [f for f in standard_features] + [f + ["debug"] for f in standard_features] + +def _standard_library_runtime_names(): + return set([_halide_library_runtime_target_name(f) for f in _standard_library_runtime_features()]) + +def halide_library_runtimes(): + runtime_package = "" + if PACKAGE_NAME != runtime_package: + fail("halide_library_runtimes can only be used from package '%s' (this is %s)" % (runtime_package, PACKAGE_NAME)) + unused = [_define_halide_library_runtime(f) for f in _standard_library_runtime_features()] + unused = unused # unused variable + + +def halide_generator(name, + srcs, + copts=[], + deps=[], + generator_name="", + includes=[], + tags=[], + visibility=None): + if not name.endswith("_generator"): + fail("halide_generator rules must end in _generator") + + if not generator_name: + generator_name = name[:-10] # strip "_generator" suffix + + native.cc_library( + name="%s_library" % name, + srcs=srcs, + alwayslink=1, + copts=copts + halide_language_copts(), + deps=set(["@halide//:language"] + deps), + tags=["manual"] + tags, + visibility=["//visibility:private"]) + + native.cc_binary( + name="%s_binary" % name, + copts=copts + halide_language_copts(), + linkopts=halide_language_linkopts(), + deps=[ + ":%s_library" % name, + "@halide//:internal_halide_generator_glue", + ], + tags=["manual"] + tags, + visibility=["//visibility:private"]) + _gengen_closure( + name="%s_closure" % name, + generator_binary="%s_binary" % name, + halide_generator_name=generator_name, + visibility=["//visibility:private"]) + + # If srcs is empty, we're building the halide-library-runtime, + # which has no stub: just skip it. + stub_gen_hdrs_target = [] + if srcs: + # The specific target doesn't matter (much), but we need + # something that is valid, so uniformly choose first entry + # so that build product cannot vary by build host + stub_header_target = _select_multitarget( + base_target=_HALIDE_TARGET_CONFIG_INFO[0][0], + halide_target_features=[], + halide_target_map={}) + _gengen( + name="%s_stub_gen" % name, + filename=name[:-10], # strip "_generator" suffix + generator_closure=":%s_closure" % name, + halide_target=stub_header_target, + outputs=["cpp_stub"], + tags=tags, + visibility=["//visibility:private"]) + stub_gen_hdrs_target = [":%s_stub_gen" % name] + + native.cc_library( + name=name, + alwayslink=1, + hdrs=stub_gen_hdrs_target, + deps=[ + ":%s_library" % name, + "@halide//:language" + ], + copts=copts + halide_language_copts(), + includes=includes, + visibility=visibility, + tags=["manual"] + tags) + + +def halide_library_from_generator(name, + generator, + debug_codegen_level=0, + deps=[], + extra_outputs=[], + function_name=None, + generator_args="", + halide_target_features=[], + halide_target_map=halide_library_default_target_map(), + hexagon_code_signer=None, + includes=[], + namespace=None, + tags=[], + visibility=None): + if not function_name: + function_name = name + + if namespace: + function_name = "%s::%s" % (namespace, function_name) + + # Escape backslashes and double quotes. + generator_args = generator_args.replace("\\", '\\\\"').replace('"', '\\"') + + if _has_dupes(halide_target_features): + fail("Duplicate values in halide_target_features: %s" % + str(halide_target_features)) + if _has_dupes(extra_outputs): + fail("Duplicate values in extra_outputs: %s" % str(extra_outputs)) + + full_halide_target_features = sorted(list(set(halide_target_features + ["c_plus_plus_name_mangling", "no_runtime"]))) + user_halide_target_features = sorted(list(set(halide_target_features))) + + if "cpp" in extra_outputs: + fail("halide_library('%s') doesn't support 'cpp' in extra_outputs; please depend on '%s_cc' instead." % (name, name)) + + outputs = ["static_library"] + extra_outputs + generator_closure = "%s_closure" % generator + + condition_deps = {} + for base_target, _, _, _ in _HALIDE_TARGET_CONFIG_INFO: + multitarget = _select_multitarget( + base_target=base_target, + halide_target_features=full_halide_target_features, + halide_target_map=halide_target_map) + arch = _halide_target_to_bazel_rule_name(base_target) + sub_name = "%s_%s" % (name, arch) + _gengen( + name=sub_name, + filename=sub_name, + halide_generator_args=generator_args, + generator_closure=generator_closure, + halide_target=multitarget, + halide_function_name=function_name, + sanitizer=select({ + "@halide//:halide_config_msan": "msan", + "//conditions:default": "", + }), + debug_codegen_level=debug_codegen_level, + hexagon_code_signer=hexagon_code_signer, + tags=["manual"] + tags, + outputs=outputs) + libname = "halide_internal_%s_%s" % (name, arch) + native.cc_library( + name=libname, + srcs=[":%s.a" % sub_name], + tags=["manual"] + tags, + visibility=["//visibility:private"]) + settings = _config_settings(base_target) + for s in settings: + condition_deps[s] = _HALIDE_RUNTIME_OVERRIDES.get(base_target, []) + [":%s" % libname] + + # Note that we always build the .h file using the first entry in + # the _HALIDE_TARGET_CONFIG_INFO table. + header_target = _select_multitarget( + base_target=_HALIDE_TARGET_CONFIG_INFO[0][0], + halide_target_features=full_halide_target_features, + halide_target_map=halide_target_map) + if len(header_target) > 1: + # This can happen if someone uses halide_target_map + # to force everything to be multitarget. In that + # case, just use the first entry. + header_target = [header_target[0]] + + outputs = ["h"] + _gengen( + name="%s_h" % name, + filename=name, + halide_generator_args=generator_args, + generator_closure=generator_closure, + halide_target=header_target, + halide_function_name=function_name, + outputs=outputs, + tags=tags) + + # Create a _cc target for (unusual) applications that want C++ source output; + # we don't support this via extra_outputs=["cpp"] because it can end up being + # compiled by Bazel, producing duplicate symbols; also, targets that want this + # sometimes want to compile it via a separate tool (e.g., XCode to produce + # certain bitcode variants). Note that this deliberately does not produce + # a cc_library() output. + _gengen( + name="%s_cc" % name, + filename=name, + halide_generator_args=generator_args, + generator_closure=generator_closure, + halide_target=header_target, + halide_function_name=function_name, + outputs=["cpp"], + tags=["manual"] + tags) + + runtime_library = _halide_library_runtime_target_name(user_halide_target_features) + if runtime_library in _standard_library_runtime_names(): + runtime_library = "@halide//:%s" % runtime_library + else: + if not native.existing_rule(runtime_library): + _define_halide_library_runtime(user_halide_target_features) + # Note to maintainers: if this message is reported, you probably want to add + # feature combination as an item in _standard_library_runtime_features() + # in this file. (Failing to do so will only cause potentially-redundant + # runtime library building, but no correctness problems.) + print("\nCreating Halide runtime library for feature set combination: " + + str(_discard_useless_features(user_halide_target_features)) + "\n" + + "If you see this message, there is no need to take any action; " + + "however, please forward this message to halide-dev@lists.csail.mit.edu " + + "so that we can include this case to reduce build times.") + runtime_library = ":%s" % runtime_library + + native.cc_library( + name=name, + hdrs=[":%s_h" % name], + # Order matters: runtime_library must come *after* condition_deps, so that + # they will be presented to the linker in this order, and we want + # unresolved symbols in the generated code (in condition_deps) to be + # resolved in the runtime library. + deps=select(condition_deps) + deps + ["@halide//:runtime", runtime_library], + includes=includes, + tags=tags, + visibility=visibility) + + # Return the fully-qualified built target name. + return "//%s:%s" % (PACKAGE_NAME, name) + +def halide_library(name, + srcs, + copts=[], + debug_codegen_level=0, + extra_outputs=[], # "stmt" and/or "assembly" are useful for debugging + filter_deps=[], + function_name=None, + generator_args="", + generator_deps=[], + generator_name=None, + halide_target_features=[], + halide_target_map=halide_library_default_target_map(), + hexagon_code_signer=None, + includes=[], + namespace=None, + visibility=None): + halide_generator( + name="%s_generator" % name, + srcs=srcs, + generator_name=generator_name, + deps=generator_deps, + includes=includes, + copts=copts, + visibility=visibility) + + return halide_library_from_generator( + name=name, + generator=":%s_generator" % name, + deps=filter_deps, + visibility=visibility, + namespace=namespace, + includes=includes, + function_name=function_name, + generator_args=generator_args, + debug_codegen_level=debug_codegen_level, + halide_target_features=halide_target_features, + halide_target_map=halide_target_map, + hexagon_code_signer=hexagon_code_signer, + extra_outputs=extra_outputs) + diff --git a/bazel/halide_linkopts.bzl.tpl b/bazel/halide_linkopts.bzl.tpl new file mode 100644 index 000000000000..c325dc058006 --- /dev/null +++ b/bazel/halide_linkopts.bzl.tpl @@ -0,0 +1,2 @@ +def halide_linkopts(): + return "%{llvm_system-libs}" diff --git a/test/scripts/build_travis.sh b/test/scripts/build_travis.sh index abeb2fff35f9..9cde7535c287 100755 --- a/test/scripts/build_travis.sh +++ b/test/scripts/build_travis.sh @@ -60,6 +60,16 @@ elif [ ${BUILD_SYSTEM} = 'MAKE' ]; then # Build the docs and run the tests make doc test_correctness test_generators + + # Build the distrib folder (needed for the Bazel build test) + make distrib + + # Build our one-and-only Bazel test. + # --verbose_failures so failures are easier to figure out. + echo "Testing apps/bazeldemo..." + cd apps/bazeldemo + bazel build --verbose_failures :all + else echo "Unexpected BUILD_SYSTEM: \"${BUILD_SYSTEM}\"" exit 1