Merge pull request #3142 from ScottTodd:main-to-google PiperOrigin-RevId: 331661857
diff --git a/build_tools/docker/build_and_update_gcr.py b/build_tools/docker/build_and_update_gcr.py deleted file mode 100755 index 6de59a4..0000000 --- a/build_tools/docker/build_and_update_gcr.py +++ /dev/null
@@ -1,158 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright 2020 Google LLC -# -# 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 -# -# https://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. -"""Builds the specified Docker images and optionally pushes them to GCR. - -Example usage: - python3 build_tools/docker/build_and_update_gcr.py --image cmake -""" - -import argparse -import functools -import os -import subprocess -import sys - -IREE_GCR_URL = 'gcr.io/iree-oss/' -DOCKER_DIR = 'build_tools/docker/' - -# Map from image names to images that they depend on. -IMAGES_TO_DEPENDENCIES = { - 'bazel': [], - 'bazel-bindings': ['bazel'], - 'bazel-tensorflow': ['bazel-bindings'], - 'bazel-nvidia': ['bazel-tensorflow'], - 'bazel-swiftshader': ['bazel-tensorflow'], - 'cmake': [], - 'cmake-android': ['cmake'], - 'cmake-nvidia': ['cmake'], - 'cmake-vulkan': ['cmake'], - 'cmake-swiftshader': ['cmake-vulkan'], - 'rbe-toolchain': [], -} - -IMAGES_TO_DEPENDENT_IMAGES = {k: [] for k in IMAGES_TO_DEPENDENCIES.keys()} -for image, dependencies in IMAGES_TO_DEPENDENCIES.items(): - for dependency in dependencies: - IMAGES_TO_DEPENDENT_IMAGES[dependency].append(image) - -IMAGES_HELP = [f'`{name}`' for name in IMAGES_TO_DEPENDENCIES.keys()] -IMAGES_HELP = f'{", ".join(IMAGES_HELP)} or `all`' - -RBE_MESSAGE = """ -Remember to update the `rbe_default` digest in the `WORKSPACE` file to reflect -the new digest for the container. - -Use `docker images --digests` to view the digest.""" - - -def parse_arguments(): - """Parses command-line options.""" - parser = argparse.ArgumentParser( - description="Build IREE's Docker images and optionally push them to GCR.") - parser.add_argument( - '--image', - dest='images', - type=str, - required=True, - action='append', - help=f'Name of the image to build: {IMAGES_HELP}.') - parser.add_argument( - '--tag', - type=str, - default='latest', - help='Tags for the images to build. Defaults to `latest` (which is good ' - 'for testing changes in a PR). Use `prod` to update the images that the ' - 'OSS CI uses.') - parser.add_argument( - '--push', - action='store_true', - help='Push the built images to GCR. Requires gcloud authorization.') - - args = parser.parse_args() - for image in args.images: - if image == 'all': - args.images = IMAGES_TO_DEPENDENCIES.keys() - elif image not in IMAGES_TO_DEPENDENCIES.keys(): - raise parser.error('Expected --image to be one of:\n' - f' {IMAGES_HELP}\n' - f'but got `{image}`.') - return args - - -def cmp_images_by_dependency(image1, image2): - if image2 in IMAGES_TO_DEPENDENT_IMAGES[image1]: - return -1 - if image1 in IMAGES_TO_DEPENDENT_IMAGES[image2]: - return 1 - return (image1 > image2) - (image1 < image2) - - -def run_command(command): - print(f'Running: {" ".join(command)}') - process = subprocess.Popen( - command, - bufsize=1, - stderr=subprocess.STDOUT, - stdout=subprocess.PIPE, - text=True) - for line in process.stdout: - print(line, end='') - - return process.poll() - - -def check_command(command): - exit_code = run_command(command) - if exit_code != 0: - print(f'Command failed: {" ".join(command)}') - sys.exit(exit_code) - - -if __name__ == '__main__': - args = parse_arguments() - - # Ensure the user has the correct authorization if they try to push to GCR. - if args.push: - if run_command(['which', 'gcloud']) != 0: - print('gcloud not found.' - ' See https://cloud.google.com/sdk/install for installation.') - sys.exit(1) - check_command(['gcloud', 'auth', 'configure-docker']) - - # Check if any images depend on `args.images` and update them if they do. - images_to_update_set = set() - to_check = list(args.images) - while to_check: - image = to_check.pop() - if image not in images_to_update_set: - images_to_update_set.add(image) - to_check.extend(IMAGES_TO_DEPENDENT_IMAGES[image]) - - # Topo sort by image dependency - images_to_update = sorted( - images_to_update_set, key=functools.cmp_to_key(cmp_images_by_dependency)) - - print(f'Also updating dependent images. Will update: {images_to_update}') - for image in images_to_update: - print(f'Updating image {image}') - image_url = os.path.join(IREE_GCR_URL, f'{image}:{args.tag}') - image_path = os.path.join(DOCKER_DIR, image.replace('-', '_')) - check_command(['docker', 'build', '--tag', image_url, image_path]) - if args.push: - check_command(['docker', 'push', image_url]) - - if 'rbe-toolchain' in images_to_update: - print(RBE_MESSAGE)
diff --git a/build_tools/docker/manage_images.py b/build_tools/docker/manage_images.py new file mode 100755 index 0000000..9e7b7e3 --- /dev/null +++ b/build_tools/docker/manage_images.py
@@ -0,0 +1,272 @@ +#!/usr/bin/env python3 + +# Copyright 2020 Google LLC +# +# 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 +# +# https://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. +"""Manages IREE Docker image definitions. + +Includes information on their dependency graph and GCR URL. + +Example usage: + +Rebuild the cmake image and all images that transitiviely on depend on it, +tagging them with `latest`: + python3 build_tools/docker/manage_images.py --build --image cmake + +Print out output for rebuilding the cmake image and all images that +transitiviely on depend on it, but don't take side-effecting actions: + python3 build_tools/docker/manage_images.py --build --image cmake --dry-run + +Push all `prod` images to GCR: + python3 build_tools/docker/manage_images.py --push --tag prod --images all + +Rebuild and push all images and update references to them in the repository: + python3 build_tools/docker/manage_images.py --push --images all + --update-references +""" + +import argparse +import fileinput +import os +import posixpath +import re +import subprocess +import sys + +IREE_GCR_URL = 'gcr.io/iree-oss/' +DOCKER_DIR = 'build_tools/docker/' + +# Map from image names to images that they depend on. +IMAGES_TO_DEPENDENCIES = { + 'bazel': [], + 'bazel-bindings': ['bazel'], + 'bazel-tensorflow': ['bazel-bindings'], + 'bazel-nvidia': ['bazel-tensorflow'], + 'bazel-swiftshader': ['bazel-tensorflow'], + 'cmake': [], + 'cmake-android': ['cmake'], + 'cmake-nvidia': ['cmake'], + 'cmake-vulkan': ['cmake'], + 'cmake-swiftshader': ['cmake-vulkan'], + 'rbe-toolchain': [], +} + +IMAGES_TO_DEPENDENT_IMAGES = {k: [] for k in IMAGES_TO_DEPENDENCIES} +for image, dependencies in IMAGES_TO_DEPENDENCIES.items(): + for dependency in dependencies: + IMAGES_TO_DEPENDENT_IMAGES[dependency].append(image) + +IMAGES_HELP = [f'`{name}`' for name in IMAGES_TO_DEPENDENCIES] +IMAGES_HELP = f'{", ".join(IMAGES_HELP)} or `all`' + + +def parse_arguments(): + """Parses command-line options.""" + parser = argparse.ArgumentParser( + description="Build IREE's Docker images and optionally push them to GCR.") + parser.add_argument( + '--images', + '--image', + type=str, + required=True, + action='append', + help=f'Name of the image to build: {IMAGES_HELP}.') + parser.add_argument( + '--tag', + type=str, + default='latest', + help='Tag for the images to build. Defaults to `latest` (which is good ' + 'for testing changes in a PR). Use `prod` to update the images that the ' + 'CI caches.') + parser.add_argument( + '--pull', + action='store_true', + help='Pull the specified image before building.') + parser.add_argument( + '--build', + action='store_true', + help='Build new images from the current Dockerfiles.') + parser.add_argument( + '--push', + action='store_true', + help='Push the built images to GCR. Requires gcloud authorization.') + parser.add_argument( + '--update_references', + '--update-references', + action='store_true', + help='Update all references to the specified images to point at the new' + ' digest.') + parser.add_argument( + '--dry_run', + '--dry-run', + '-n', + action='store_true', + help='Print output without building or pushing any images.') + + args = parser.parse_args() + for image in args.images: + if image == 'all': + # Sort for a determinstic order + args.images = sorted(IMAGES_TO_DEPENDENCIES.keys()) + elif image not in IMAGES_TO_DEPENDENCIES: + raise parser.error('Expected --image to be one of:\n' + f' {IMAGES_HELP}\n' + f'but got `{image}`.') + return args + + +def get_ordered_images_to_process(images): + unmarked_images = list(images) + # Python doesn't have a builtin OrderedSet + marked_images = set() + order = [] + + def visit(image): + if image in marked_images: + return + for dependent_images in IMAGES_TO_DEPENDENT_IMAGES[image]: + visit(dependent_images) + marked_images.add(image) + order.append(image) + + while unmarked_images: + visit(unmarked_images.pop()) + + order.reverse() + return order + + +def stream_command(command, dry_run=False): + print(f'Running: `{" ".join(command)}`') + if dry_run: + return 0 + process = subprocess.Popen( + command, + bufsize=1, + stderr=subprocess.STDOUT, + stdout=subprocess.PIPE, + universal_newlines=True) + for line in process.stdout: + print(line, end='') + + if process.poll() is None: + raise RuntimeError('Unexpected end of output while process is not finished') + return process.poll() + + +def check_stream_command(command, dry_run=False): + exit_code = stream_command(command, dry_run=dry_run) + if exit_code != 0: + print(f'Command failed with exit code {exit_code}: `{" ".join(command)}`') + sys.exit(exit_code) + + +def get_repo_digest(image): + inspect_command = [ + 'docker', + 'image', + 'inspect', + f'{image}', + '-f', + '{{index .RepoDigests 0}}', + ] + inspect_process = subprocess.run( + inspect_command, + universal_newlines=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=10) + if inspect_process.returncode != 0: + print(f'Computing the repository digest for {image} failed.' + ' Has it been pushed to GCR?') + print(f'Output from `{" ".join(inspect_command)}`:') + print(inspect_process.stdout, end='') + print(inspect_process.stderr, end='') + sys.exit(inspect_process.returncode) + _, repo_digest = inspect_process.stdout.strip().split('@') + return repo_digest + + +def update_rbe_reference(digest, dry_run=False): + print('Updating WORKSPACE file for rbe-toolchain') + for line in fileinput.input(files=['WORKSPACE'], inplace=(not dry_run)): + if line.strip().startswith('digest ='): + print(re.sub('sha256:[a-zA-Z0-9]+', digest, line), end='') + else: + print(line, end='') + + +def update_references(image_name, digest, dry_run=False): + print(f'Updating references to {image_name}') + + grep_command = ['git', 'grep', '-l', f'{image_name}@sha256'] + grep_process = subprocess.run( + grep_command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + timeout=5, + universal_newlines=True) + if grep_process.returncode > 1: + print(f'{" ".join(grep_command)} ' + f'failed with exit code {grep_process.returncode}') + sys.exit(grep_process.returncode) + if grep_process.returncode == 1: + print(f'Found no references to {image_name}') + return + + files = grep_process.stdout.split() + print(f'Updating references in {len(files)} files: {files}') + for line in fileinput.input(files=files, inplace=(not dry_run)): + print( + re.sub(f'{image_name}@sha256:[a-zA-Z0-9]+', f'{image_name}@{digest}', + line), + end='') + + +if __name__ == '__main__': + args = parse_arguments() + + # Ensure the user has the correct authorization if they try to push to GCR. + if args.push: + if stream_command(['which', 'gcloud']) != 0: + print('gcloud not found.' + ' See https://cloud.google.com/sdk/install for installation.') + sys.exit(1) + check_stream_command(['gcloud', 'auth', 'configure-docker'], + dry_run=args.dry_run) + + images_to_process = get_ordered_images_to_process(args.images) + print(f'Also processing dependent images. Will process: {images_to_process}') + + for image in images_to_process: + print(f'Processing image {image}') + image_name = posixpath.join(IREE_GCR_URL, image) + image_tag = f'{image_name}:{args.tag}' + image_path = os.path.join(DOCKER_DIR, image.replace('-', '_')) + + if args.pull: + check_stream_command(['docker', 'pull', image_tag], dry_run=args.dry_run) + + if args.build: + check_stream_command(['docker', 'build', '--tag', image_tag, image_path], + dry_run=args.dry_run) + + if args.push: + check_stream_command(['docker', 'push', image_tag], dry_run=args.dry_run) + + if args.update_references: + digest = get_repo_digest(image_tag) + # Just hardcode this oddity + if image == 'rbe-toolchain': + update_rbe_reference(digest, dry_run=args.dry_run) + update_references(image_name, digest, dry_run=args.dry_run)
diff --git a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-swiftshader/integrations/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-swiftshader/integrations/build_kokoro.sh index 63c5893..b8372a2 100755 --- a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-swiftshader/integrations/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-swiftshader/integrations/build_kokoro.sh
@@ -33,7 +33,7 @@ docker run "${DOCKER_RUN_ARGS[@]?}" \ --env IREE_VULKAN_DISABLE=0 \ - gcr.io/iree-oss/bazel-swiftshader:prod \ + gcr.io/iree-oss/bazel-swiftshader@sha256:59ca639199c1548d3fd2c9f6bcc04ccd91c6a32fd514a4ea4e2b8542e7b0eed2 \ build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-swiftshader/integrations/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-turing/integrations/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-turing/integrations/build_kokoro.sh index 0712a66..4f6f453 100755 --- a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-turing/integrations/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-turing/integrations/build_kokoro.sh
@@ -34,7 +34,7 @@ --rm \ --env IREE_VULKAN_DISABLE=0 \ --gpus all \ - gcr.io/iree-oss/bazel-nvidia:prod \ + gcr.io/iree-oss/bazel-nvidia@sha256:77866668ac679de65f5008c3c3df0e5dbf2944a431c88a8c1b6b2e8ab7f8c65d \ build_tools/kokoro/gcp_ubuntu/bazel/linux/x86-turing/integrations/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/bindings/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/bindings/build_kokoro.sh index 24cbabf..ce85c9c 100755 --- a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/bindings/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/bindings/build_kokoro.sh
@@ -32,7 +32,7 @@ docker_setup docker run "${DOCKER_RUN_ARGS[@]?}" \ - gcr.io/iree-oss/bazel-bindings:prod \ + gcr.io/iree-oss/bazel-bindings@sha256:1f5e59f10c35d0f9211c1a8821a931aca746f47a66fe1bc31b8b3bad4f38a0a7 \ build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/bindings/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/core/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/core/build_kokoro.sh index da07cdd..da01f66 100755 --- a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/core/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/core/build_kokoro.sh
@@ -32,7 +32,7 @@ docker_setup docker run "${DOCKER_RUN_ARGS[@]?}" \ - gcr.io/iree-oss/bazel:prod \ + gcr.io/iree-oss/bazel@sha256:b3ce4db78ccfc175cad0b071075cb4b26845980dacd7afa51f39625876062476 \ build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/core/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/integrations/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/integrations/build_kokoro.sh index c35d897..ca204ca 100755 --- a/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/integrations/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/integrations/build_kokoro.sh
@@ -32,7 +32,7 @@ docker_setup docker run "${DOCKER_RUN_ARGS[@]?}" \ - gcr.io/iree-oss/bazel-tensorflow:prod \ + gcr.io/iree-oss/bazel-tensorflow@sha256:97045a41e101c7870112e59e39a94e0e3a6dbe376a5bb189cc85b9474bff75a0 \ build_tools/kokoro/gcp_ubuntu/bazel/linux/x86/integrations/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/cmake/android/arm64-v8a/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/cmake/android/arm64-v8a/build_kokoro.sh index 329e226..3f22a1c 100755 --- a/build_tools/kokoro/gcp_ubuntu/cmake/android/arm64-v8a/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/cmake/android/arm64-v8a/build_kokoro.sh
@@ -32,7 +32,7 @@ docker_setup docker run "${DOCKER_RUN_ARGS[@]?}" \ - gcr.io/iree-oss/cmake-android:prod \ + gcr.io/iree-oss/cmake-android@sha256:5efb3d61e26be5ea8f26313d858c11faff5c1ed1fd83b7051d6e1c3309265e01 \ build_tools/kokoro/gcp_ubuntu/cmake/android/build.sh arm64-v8a # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-swiftshader/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-swiftshader/build_kokoro.sh index 82426ec..362eaf9 100755 --- a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-swiftshader/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-swiftshader/build_kokoro.sh
@@ -33,7 +33,7 @@ docker run "${DOCKER_RUN_ARGS[@]?}" \ --env IREE_VULKAN_DISABLE=0 \ - gcr.io/iree-oss/cmake-swiftshader:prod \ + gcr.io/iree-oss/cmake-swiftshader@sha256:1912ed3a5f85c8d9abd0729834711905ef4ef03b381eb7f99d9fdb7867932d30 \ build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-swiftshader/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-turing/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-turing/build_kokoro.sh index 0b2364a..518dcd8 100755 --- a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-turing/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-turing/build_kokoro.sh
@@ -34,7 +34,7 @@ docker run "${DOCKER_RUN_ARGS[@]?}" \ --env IREE_VULKAN_DISABLE=0 \ --gpus all \ - gcr.io/iree-oss/cmake-nvidia:prod \ + gcr.io/iree-oss/cmake-nvidia@sha256:26b6fcc4005b4cbb23988b9a8d5484355dd58f5a4a8df10537df8fceb1d7e26a \ build_tools/kokoro/gcp_ubuntu/cmake/linux/x86-turing/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86/build_kokoro.sh b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86/build_kokoro.sh index ee459ff..b3d684e 100755 --- a/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86/build_kokoro.sh +++ b/build_tools/kokoro/gcp_ubuntu/cmake/linux/x86/build_kokoro.sh
@@ -31,7 +31,7 @@ docker_setup docker run "${DOCKER_RUN_ARGS[@]?}" \ - gcr.io/iree-oss/cmake:prod \ + gcr.io/iree-oss/cmake@sha256:da2de0066bf5e9607fe35c4e7816b19e779d47cd29a0a5f889a66c9c64aded57 \ build_tools/kokoro/gcp_ubuntu/cmake/linux/x86/build.sh # Kokoro will rsync this entire directory back to the executor orchestrating the
diff --git a/docs/design_docs/metal_hal_driver.md b/docs/design_docs/metal_hal_driver.md index 89ade9a..0729d9a 100644 --- a/docs/design_docs/metal_hal_driver.md +++ b/docs/design_docs/metal_hal_driver.md
@@ -58,6 +58,8 @@ [`hal::CommandQueue`][hal-command-queue] | [`MTLCommandQueue`][mtl-command-queue] [`hal::CommandBuffer`][hal-command-buffer] | [`MTLCommandBuffer`][mtl-command-buffer] [`hal::Semaphore`][hal-semaphore] | [`MTLSharedEvent`][mtl-shared-event] +[`hal::Allocator`][hal-allocator] | N/A +[`hal::Buffer`][hal-buffer] | [`MTLBuffer`][mtl-buffer] In the following subsections, we go over each pair to provide more details. @@ -124,25 +126,77 @@ `notifyListener:atValue:block:` to singal a semaphore to wake the current thread, which is put into sleep by waiting on the semaphore. +### Allocator + +At the moment the Metal HAL driver just has a very simple +[`hal::Allocator`][hal-allocator] implementation. It just wraps a `MTLDevice` +and redirects all allocation requests to the `MTLDevice`. No page/pool/slab or +whatever. This is only meant to get started. In the future we should have a +better memory allocation library, probably by layering the +[Vulkan Memory Allocator][vma] on top of [`MTLHeap`][mtl-heap]. + +### Buffer + +IREE [`hal::Buffer`][hal-buffer] maps Metal `MTLBuffer`. See +[Memory Management](#memory-management) for more details. + +## Memory Management + +### Storage type + +Metal provides four [`MTLStorageMode`][mtl-storage-mode] options: + +* `MTLStorageModeShared`: The resource is stored in system memory and is + accessible to both the CPU and the GPU. +* `MTLStorageModeManaged`: The CPU and GPU may maintain separate copies of the + resource, and any changes must be explicitly synchronized. +* `MTLStorageModePrivate`: The resource can be accessed only by the GPU. +* `MTLStorageMemoryless`: The resource’s contents can be accessed only by the + GPU and only exist temporarily during a render pass. + +Among them, `MTLStorageModeManaged` is only available on macOS. + +IREE HAL defines serveral [`MemoryType`][hal-buffer]. They need to map to the +above storage modes: + +* If `kDeviceLocal` but not `kHostVisible`, `MTLStorageModePrivate` is chosen. +* If `kDeviceLocal` and `kHostVisible`: + * If macOS, `MTLStorageModeManaged` can be chosen. + * Otherwise, `MTLStorageModeShared` is chosen. +* If not `DeviceLocal` but `kDeviceVisible`, `MTLStorageModeShared` is chosen. +* If not `kDeviceLocal` and not `kDeviceVisible`, `MTLStorageModeShared` is + chosen. (TODO: We should probably use host buffer here.) + +IREE HAL also allows to create buffers with `kHostCoherent` bit. This may still +be backed by `MTLStorageModeManaged` `MTLBuffer`s in macOS. To respect the +`kHostCoherent` protocol, the Metal HAL driver will perform necessary +`InValidate`/`Flush` operations automatically under the hood. + [macos-version-share]: https://gs.statcounter.com/macos-version-market-share/desktop/worldwide [ios-version-share]: https://developer.apple.com/support/app-store/ [iree-hal]: https://github.com/google/iree/tree/main/iree/hal [iree-metal]: https://github.com/google/iree/tree/main/iree/hal/metal [iree-refptr]: https://github.com/google/iree/blob/main/iree/base/ref_ptr.h -[hal-driver]: https://github.com/google/iree/blob/main/iree/hal/driver.h -[hal-device]: https://github.com/google/iree/blob/main/iree/hal/device.h +[hal-allocator]: https://github.com/google/iree/blob/main/iree/hal/allocator.h +[hal-buffer]: https://github.com/google/iree/blob/main/iree/hal/buffer.h [hal-command-queue]: https://github.com/google/iree/blob/main/iree/hal/command_queue.h [hal-command-buffer]: https://github.com/google/iree/blob/main/iree/hal/command_buffer.h +[hal-device]: https://github.com/google/iree/blob/main/iree/hal/device.h +[hal-driver]: https://github.com/google/iree/blob/main/iree/hal/driver.h [hal-semaphore]: https://github.com/google/iree/blob/main/iree/hal/semaphore.h -[metal-driver]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_driver.h -[metal-device]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_device.h [metal-command-queue]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_command_queue.h [metal-command-buffer]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_command_buffer.h +[metal-device]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_device.h +[metal-driver]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_driver.h [metal-shared-event]: https://github.com/google/iree/blob/main/iree/hal/metal/metal_shared_event.h -[mtl-device]: https://developer.apple.com/documentation/metal/mtldevice?language=objc -[mtl-command-queue]: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc +[mtl-buffer]: https://developer.apple.com/documentation/metal/mtlbuffer?language=objc [mtl-command-buffer]: https://developer.apple.com/documentation/metal/mtlcommandbuffer?language=objc [mtl-command-encoder]: https://developer.apple.com/documentation/metal/mtlcommandencoder?language=objc +[mtl-command-queue]: https://developer.apple.com/documentation/metal/mtlcommandqueue?language=objc +[mtl-device]: https://developer.apple.com/documentation/metal/mtldevice?language=objc +[mtl-heap]: https://developer.apple.com/documentation/metal/mtlheap?language=objc [mtl-shared-event]: https://developer.apple.com/documentation/metal/mtlsharedevent?language=objc +[mtl-storage-mode]: https://developer.apple.com/documentation/metal/mtlstoragemode?language=objc [objc-arc]: https://en.wikipedia.org/wiki/Automatic_Reference_Counting [objcxx]: https://en.wikipedia.org/wiki/Objective-C#Objective-C++ +[vma]: https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator
diff --git a/integrations/tensorflow/e2e/bool_test.py b/integrations/tensorflow/e2e/bool_test.py index 2b29f2e..161f6ad 100644 --- a/integrations/tensorflow/e2e/bool_test.py +++ b/integrations/tensorflow/e2e/bool_test.py
@@ -21,6 +21,10 @@ class MathModule(tf.Module): + @tf.function(input_signature=[]) + def constant(self): + return np.array([True, False, True], dtype=np.bool) + @tf.function(input_signature=[tf.TensorSpec([4], tf.float32)]) def greater_than(self, x): return x > 1.0 @@ -36,6 +40,13 @@ @tf_test_utils.compile_module(MathModule) class BooleanTest(tf_test_utils.TracedModuleTestCase): + def test_constant(self): + + def constant(module): + module.constant() + + self.compare_backends(constant) + def test_greater_than(self): def greater_than(module):
diff --git a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/ConvertTensorOps.cpp b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/ConvertTensorOps.cpp index 6fcadbd..6efc149 100644 --- a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/ConvertTensorOps.cpp +++ b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/ConvertTensorOps.cpp
@@ -57,9 +57,25 @@ IREE::HAL::BufferUsageBitfield::All | IREE::HAL::BufferUsageBitfield::Constant; + auto elementsAttr = constantOp.getValue().cast<ElementsAttr>(); + auto elementsTy = elementsAttr.getType().cast<ShapedType>(); + + // Expand boolean elements to the minimum bit widht supported by the HAL + // (8-bits). + // To improve memory bandwidth and increase computae we should prefer to + // pack 1-bit tensors into wider storage before this lossy conversion. For + // example bitwise ops on 8x32xi1 can be converted to ops on tensor<8xi32>. + if (elementsTy.getElementType().isInteger(1)) { + elementsAttr = + elementsAttr.mapValues(rewriter.getIntegerType(8), + llvm::function_ref<APInt(const APInt &val)>( + [](const APInt &val) -> APInt { + return APInt(8, val.getBoolValue()); + })); + } + auto buffer = rewriter.createOrFold<IREE::HAL::AllocatorAllocateConstOp>( - constantOp.getLoc(), allocator, memoryTypes, bufferUsage, - constantOp.getValue().cast<ElementsAttr>()); + constantOp.getLoc(), allocator, memoryTypes, bufferUsage, elementsAttr); // TODO(benvanik): implement resource sets. rewriter.create<IREE::HAL::ExDeferReleaseOp>(constantOp.getLoc(), buffer);
diff --git a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/stream_ops.mlir b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/stream_ops.mlir index aad814a..084af52 100644 --- a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/stream_ops.mlir +++ b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/stream_ops.mlir
@@ -27,14 +27,13 @@ // CHECK: %[[CMD:.+]] = hal.command_buffer.create {{.+}}, "OneShot", "Transfer|Dispatch" // CHECK-NEXT: hal.command_buffer.begin %[[CMD]] %0 = flow.ex.stream.fragment(%arg1 = %cst : index, %arg2 = %arg0 : tensor<128xf32>) -> tensor<128xf32> { - // CHECK-DAG: %[[EXE:.+]] = hal.executable.lookup {{.+}}, @ex0 : !hal.executable // CHECK-DAG: %[[EXE_LAYOUT:.+]] = hal.executable_layout.lookup // CHECK: hal.command_buffer.push_descriptor_set %[[CMD]], %[[EXE_LAYOUT]], set=0, bindings=[0 = (%arg0, %c0, %sz_3), 1 = (%buffer_1, %c0, %sz_4)] - // CHECK: hal.command_buffer.dispatch.symbol {{.+}}, entry_point = @ex0::@vmla::@entry0, workgroup_xyz + // CHECK: hal.command_buffer.dispatch.symbol {{.+}}, @ex0::@vmla::@entry0, workgroup_xyz // CHECK: hal.command_buffer.execution_barrier %1 = flow.dispatch @ex0::@entry0[%arg1 : index](%arg2) : (tensor<128xf32>) -> tensor<128xf32> // CHECK: hal.command_buffer.push_descriptor_set - // CHECK: hal.command_buffer.dispatch.symbol {{.+}}, entry_point = @ex0::@vmla::@entry0, workgroup_xyz + // CHECK: hal.command_buffer.dispatch.symbol {{.+}}, @ex0::@vmla::@entry0, workgroup_xyz // CHECK: hal.command_buffer.execution_barrier %2 = flow.dispatch @ex0::@entry0[%arg1 : index](%1) : (tensor<128xf32>) -> tensor<128xf32> flow.return %2 : tensor<128xf32>
diff --git a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/tensor_ops.mlir b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/tensor_ops.mlir index 52d67f8..89bd590 100644 --- a/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/tensor_ops.mlir +++ b/iree/compiler/Dialect/HAL/Conversion/FlowToHAL/test/tensor_ops.mlir
@@ -15,7 +15,7 @@ func @constantTensor1() { // CHECK-NEXT: %dev = hal.ex.shared_device // CHECK-NEXT: %allocator = hal.device.allocator %dev - // CHECK-NEXT: %cbuffer = hal.allocator.allocate.const %allocator, {{.+}} = dense<[true, false]> : tensor<2xi1> + // CHECK-NEXT: %cbuffer = hal.allocator.allocate.const %allocator, {{.+}} = dense<[1, 0]> : tensor<2xi8> %0 = constant dense<[1, 0]> : tensor<2xi1> return }
diff --git a/iree/compiler/Dialect/HAL/IR/HALOpFolders.cpp b/iree/compiler/Dialect/HAL/IR/HALOpFolders.cpp index efdd27a..626fc01 100644 --- a/iree/compiler/Dialect/HAL/IR/HALOpFolders.cpp +++ b/iree/compiler/Dialect/HAL/IR/HALOpFolders.cpp
@@ -256,6 +256,36 @@ } //===----------------------------------------------------------------------===// +// iree::hal::CommandBuffer +//===----------------------------------------------------------------------===// + +namespace { + +/// Skips a hal.command_buffer.device accessor when the device was created in +/// the same scope. +struct SkipCommandBufferDeviceOp + : public OpRewritePattern<CommandBufferDeviceOp> { + using OpRewritePattern<CommandBufferDeviceOp>::OpRewritePattern; + + LogicalResult matchAndRewrite(CommandBufferDeviceOp op, + PatternRewriter &rewriter) const override { + if (auto createOp = dyn_cast_or_null<CommandBufferCreateOp>( + op.command_buffer().getDefiningOp())) { + rewriter.replaceOp(op, createOp.device()); + return success(); + } + return failure(); + } +}; + +} // namespace + +void CommandBufferDeviceOp::getCanonicalizationPatterns( + OwningRewritePatternList &results, MLIRContext *context) { + results.insert<SkipCommandBufferDeviceOp>(context); +} + +//===----------------------------------------------------------------------===// // hal.device.switch //===----------------------------------------------------------------------===//
diff --git a/iree/compiler/Dialect/HAL/IR/HALOps.cpp b/iree/compiler/Dialect/HAL/IR/HALOps.cpp index 1f9e448..540234b 100644 --- a/iree/compiler/Dialect/HAL/IR/HALOps.cpp +++ b/iree/compiler/Dialect/HAL/IR/HALOps.cpp
@@ -853,10 +853,9 @@ void CommandBufferDispatchSymbolOp::build( OpBuilder &builder, OperationState &state, Value commandBuffer, - Value executable, IREE::HAL::ExecutableEntryPointOp entryPoint, - Value workgroupX, Value workgroupY, Value workgroupZ) { - state.addOperands( - {commandBuffer, executable, workgroupX, workgroupY, workgroupZ}); + IREE::HAL::ExecutableEntryPointOp entryPoint, Value workgroupX, + Value workgroupY, Value workgroupZ) { + state.addOperands({commandBuffer, workgroupX, workgroupY, workgroupZ}); // Construct Executable::Target::EntryPoint nested reference. StringRef executableOpSymName = entryPoint.getParentOp()
diff --git a/iree/compiler/Dialect/HAL/IR/HALOps.td b/iree/compiler/Dialect/HAL/IR/HALOps.td index 3f3f70e..42856a8 100644 --- a/iree/compiler/Dialect/HAL/IR/HALOps.td +++ b/iree/compiler/Dialect/HAL/IR/HALOps.td
@@ -1001,6 +1001,24 @@ let assemblyFormat = "$command_buffer attr-dict"; } +def HAL_CommandBufferDeviceOp : HAL_PureOp<"command_buffer.device"> { + let summary = [{command buffer device query operation}]; + let description = [{ + Used during conversion to access the device used to create a command buffer. + }]; + + let arguments = (ins + HAL_CommandBuffer:$command_buffer + ); + let results = (outs + HAL_Device:$device + ); + + let assemblyFormat = "$command_buffer attr-dict `:` type($device)"; + + let hasCanonicalizer = 1; +} + def HAL_CommandBufferExecutionBarrierOp : HAL_Op<"command_buffer.execution_barrier", [ AttrSizedOperandSegments, ]> { @@ -1179,15 +1197,13 @@ %x = constant 128 : index %y = constant 32 : index %z = constant 1 : index - hal.command_buffer.dispatch.symbol %cmd, %executable, - entry_point = @executable::@target::@entry0, + hal.command_buffer.dispatch.symbol %cmd, @executable::@target::@entry, workgroup_xyz = [%x, %y, %z] ``` }]; let arguments = (ins HAL_CommandBuffer:$command_buffer, - HAL_Executable:$executable, // TODO(scotttodd): remove this and extract from nested ref? SymbolRefAttr:$entry_point, HAL_Dim:$workgroup_x, HAL_Dim:$workgroup_y, @@ -1195,7 +1211,7 @@ ); let assemblyFormat = [{ - $command_buffer `,` $executable `,` `entry_point` `=` $entry_point `,` + $command_buffer `,` $entry_point `,` `workgroup_xyz` `=` `[` $workgroup_x `,` $workgroup_y `,` $workgroup_z `]` attr-dict }]; @@ -1204,7 +1220,7 @@ let builders = [ OpBuilder<[{ OpBuilder &builder, OperationState &state, Value commandBuffer, - Value executable, IREE::HAL::ExecutableEntryPointOp entryPoint, + IREE::HAL::ExecutableEntryPointOp entryPoint, Value workgroupX, Value workgroupY, Value workgroupZ }]>, ]; @@ -1248,22 +1264,20 @@ given buffer, using using a nested symbol reference to the entry point. ```mlir - hal.command_buffer.dispatch.indirect.symbol %cmd, %executable, - entry_point = @executable::@target::@entry0, + hal.command_buffer.dispatch.indirect.symbol %cmd, @executable::@target::@entry, workgroups = %buffer[%offset] ``` }]; let arguments = (ins HAL_CommandBuffer:$command_buffer, - HAL_Executable:$executable, // TODO(scotttodd): remove this and extract from nested ref? SymbolRefAttr:$entry_point, HAL_Buffer:$workgroups_buffer, HAL_DeviceSize:$workgroups_offset ); let assemblyFormat = [{ - $command_buffer `,` $executable `,` `entry_point` `=` $entry_point `,` + $command_buffer `,` $entry_point `,` `workgroups` `=` $workgroups_buffer `[` $workgroups_offset `]` attr-dict }]; }
diff --git a/iree/compiler/Dialect/HAL/IR/test/command_buffer_folding.mlir b/iree/compiler/Dialect/HAL/IR/test/command_buffer_folding.mlir new file mode 100644 index 0000000..a004780 --- /dev/null +++ b/iree/compiler/Dialect/HAL/IR/test/command_buffer_folding.mlir
@@ -0,0 +1,16 @@ +// Tests folding and canonicalization of HAL command buffer ops. + +// RUN: iree-opt -split-input-file -canonicalize %s | iree-opt -split-input-file | IreeFileCheck %s + +// CHECK-LABEL: @skip_command_buffer_device +func @skip_command_buffer_device() -> !hal.executable { + %dev = hal.ex.shared_device : !hal.device + %cmd = hal.command_buffer.create %dev, "OneShot", "Transfer|Dispatch" : !hal.command_buffer + + // CHECK-NOT: hal.command_buffer.device + // CHECK: %[[EXECUTABLE:.+]] = hal.executable.lookup %dev, @executable_name : !hal.executable + %0 = hal.command_buffer.device %cmd : !hal.device + %exe = hal.executable.lookup %0, @executable_name : !hal.executable + + return %exe : !hal.executable +}
diff --git a/iree/compiler/Dialect/HAL/IR/test/command_buffer_ops.mlir b/iree/compiler/Dialect/HAL/IR/test/command_buffer_ops.mlir index 168725d..c2e77bc 100644 --- a/iree/compiler/Dialect/HAL/IR/test/command_buffer_ops.mlir +++ b/iree/compiler/Dialect/HAL/IR/test/command_buffer_ops.mlir
@@ -42,6 +42,15 @@ // ----- +// CHECK-LABEL: @command_buffer_device +func @command_buffer_device(%arg0 : !hal.command_buffer) { + // CHECK: %0 = hal.command_buffer.device %arg0 : !hal.device + %0 = hal.command_buffer.device %arg0 : !hal.device + return +} + +// ----- + // CHECK-LABEL: @command_buffer_execution_barrier func @command_buffer_execution_barrier(%arg0 : !hal.command_buffer) { %0 = "test_hal.buffer"() : () -> !hal.buffer @@ -115,12 +124,11 @@ } } } - %0 = "test_hal.executable"() : () -> !hal.executable - %1 = "test_hal.workgroup_x"() : () -> index - %2 = "test_hal.workgroup_y"() : () -> index - %3 = "test_hal.workgroup_z"() : () -> index - // CHECK: hal.command_buffer.dispatch.symbol %arg0, %0, entry_point = @ex::@backend::@entry0, workgroup_xyz = [%1, %2, %3] - hal.command_buffer.dispatch.symbol %arg0, %0, entry_point = @ex::@backend::@entry0, workgroup_xyz = [%1, %2, %3] + %0 = "test_hal.workgroup_x"() : () -> index + %1 = "test_hal.workgroup_y"() : () -> index + %2 = "test_hal.workgroup_z"() : () -> index + // CHECK: hal.command_buffer.dispatch.symbol %arg0, @ex::@backend::@entry0, workgroup_xyz = [%0, %1, %2] + hal.command_buffer.dispatch.symbol %arg0, @ex::@backend::@entry0, workgroup_xyz = [%0, %1, %2] return } @@ -135,10 +143,9 @@ } } } - %0 = "test_hal.executable"() : () -> !hal.executable - %1 = "test_hal.buffer"() : () -> !hal.buffer - %2 = "test_hal.offset"() : () -> index - // CHECK: hal.command_buffer.dispatch.indirect.symbol %arg0, %0, entry_point = @ex::@backend::@entry0, workgroups = %1[%2] - hal.command_buffer.dispatch.indirect.symbol %arg0, %0, entry_point = @ex::@backend::@entry0, workgroups = %1[%2] + %0 = "test_hal.buffer"() : () -> !hal.buffer + %1 = "test_hal.offset"() : () -> index + // CHECK: hal.command_buffer.dispatch.indirect.symbol %arg0, @ex::@backend::@entry0, workgroups = %0[%1] + hal.command_buffer.dispatch.indirect.symbol %arg0, @ex::@backend::@entry0, workgroups = %0[%1] return }
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/AOT/LLVMAOTTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/AOT/LLVMAOTTarget.cpp index 0107c79..6fa091b 100644 --- a/iree/compiler/Dialect/HAL/Target/LLVM/AOT/LLVMAOTTarget.cpp +++ b/iree/compiler/Dialect/HAL/Target/LLVM/AOT/LLVMAOTTarget.cpp
@@ -42,8 +42,9 @@ LLVMAOTTargetBackend(LLVMTargetOptions options) : options_(std::move(options)) {} - // NOTE: we could vary this based on the options, such as by arch/etc. - std::string name() const override { return "dylib*"; } + // NOTE: we could vary these based on the options, such as by arch/etc. + std::string name() const override { return "llvm_aot"; } + std::string filter_pattern() const override { return "dylib*"; } void getDependentDialects(DialectRegistry& registry) const override { // clang-format off @@ -55,17 +56,6 @@ // clang-format on } - void declareTargetOps(IREE::Flow::ExecutableOp sourceOp, - IREE::HAL::ExecutableOp executableOp) override { - OpBuilder targetBuilder(&executableOp.getBlock().back()); - auto targetContainerOp = - targetBuilder.create<IREE::HAL::ExecutableTargetOp>( - sourceOp.getLoc(), /*name=*/"llvm_aot", - /*targetBackendFilter=*/name()); - OpBuilder containerBuilder(&targetContainerOp.getBlock().back()); - containerBuilder.create<ModuleOp>(sourceOp.getLoc()); - } - void buildTranslationPassPipeline(ExecutableTargetOp targetOp, OpPassManager& passManager) override { buildLLVMTransformPassPipeline(passManager);
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/IR/LLVMIRTarget.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/IR/LLVMIRTarget.cpp index 9d32820..fac9612 100644 --- a/iree/compiler/Dialect/HAL/Target/LLVM/IR/LLVMIRTarget.cpp +++ b/iree/compiler/Dialect/HAL/Target/LLVM/IR/LLVMIRTarget.cpp
@@ -39,8 +39,9 @@ LLVMIRTargetBackend(LLVMTargetOptions options) : options_(std::move(options)) {} - // NOTE: we could vary this based on the options, such as by arch/etc. - std::string name() const override { return "llvm-ir*"; } + // NOTE: we could vary these based on the options, such as by arch/etc. + std::string name() const override { return "llvm_ir"; } + std::string filter_pattern() const override { return "llvm-ir*"; } void getDependentDialects(DialectRegistry& registry) const override { // clang-format off @@ -52,17 +53,6 @@ // clang-format on } - void declareTargetOps(IREE::Flow::ExecutableOp sourceOp, - IREE::HAL::ExecutableOp executableOp) override { - OpBuilder targetBuilder(&executableOp.getBlock().back()); - auto targetContainerOp = - targetBuilder.create<IREE::HAL::ExecutableTargetOp>( - sourceOp.getLoc(), /*name=*/"llvm_ir", - /*targetBackendFilter=*/name()); - OpBuilder containerBuilder(&targetContainerOp.getBlock().back()); - containerBuilder.create<ModuleOp>(sourceOp.getLoc()); - } - void buildTranslationPassPipeline(ExecutableTargetOp targetOp, OpPassManager& passManager) override { buildLLVMTransformPassPipeline(passManager);
diff --git a/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp b/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp index ac21f6b..b126991 100644 --- a/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp +++ b/iree/compiler/Dialect/HAL/Target/TargetBackend.cpp
@@ -75,7 +75,7 @@ IREE::HAL::ExecutableOp executableOp) { OpBuilder targetBuilder(&executableOp.getBlock().back()); auto targetContainerOp = targetBuilder.create<IREE::HAL::ExecutableTargetOp>( - sourceOp.getLoc(), /*name=*/name(), /*targetBackendFilter=*/name()); + sourceOp.getLoc(), name(), filter_pattern()); OpBuilder containerBuilder(&targetContainerOp.getBlock().back()); containerBuilder.create<ModuleOp>(sourceOp.getLoc()); } @@ -135,24 +135,22 @@ Location loc, DispatchState dispatchState, DeviceSwitchBuilder &switchBuilder) { auto *region = switchBuilder.addConditionRegion( - IREE::HAL::DeviceMatchIDAttr::get(name(), loc.getContext()), + IREE::HAL::DeviceMatchIDAttr::get(filter_pattern(), loc.getContext()), { dispatchState.workload, dispatchState.commandBuffer, - dispatchState.executable, }); auto &entryBlock = region->front(); auto workload = entryBlock.getArgument(0); auto commandBuffer = entryBlock.getArgument(1); - auto executable = entryBlock.getArgument(2); auto builder = OpBuilder::atBlockBegin(&entryBlock); auto workgroupCount = calculateDispatchWorkgroupCount( loc, dispatchState.executableOp, dispatchState.entryPointOp, workload, builder); builder.create<IREE::HAL::CommandBufferDispatchSymbolOp>( - loc, commandBuffer, executable, dispatchState.entryPointOp, - workgroupCount[0], workgroupCount[1], workgroupCount[2]); + loc, commandBuffer, dispatchState.entryPointOp, workgroupCount[0], + workgroupCount[1], workgroupCount[2]); builder.create<IREE::HAL::ReturnOp>(loc); return success();
diff --git a/iree/compiler/Dialect/HAL/Target/TargetBackend.h b/iree/compiler/Dialect/HAL/Target/TargetBackend.h index f087a48..7182a4d 100644 --- a/iree/compiler/Dialect/HAL/Target/TargetBackend.h +++ b/iree/compiler/Dialect/HAL/Target/TargetBackend.h
@@ -118,9 +118,11 @@ virtual ~TargetBackend() = default; - // Returns the name of the backend as expected to be matched with a call to - // matchPattern. For example, 'vulkan-v1.1' or 'vmla*'. + // Returns a name for the backend used to differentiate between other targets. virtual std::string name() const = 0; + // Returns a filter pattern for the backend as expected to be matched with a + // call to matchPattern. For example, 'vulkan-v1.1' or 'vmla*'. + virtual std::string filter_pattern() const = 0; // Creates an interface representing the bindings and push constants required // to dispatch the executable. Interfaces used across backends and executables
diff --git a/iree/compiler/Dialect/HAL/Target/VMLA/VMLATarget.cpp b/iree/compiler/Dialect/HAL/Target/VMLA/VMLATarget.cpp index 3db3986..ae429f5 100644 --- a/iree/compiler/Dialect/HAL/Target/VMLA/VMLATarget.cpp +++ b/iree/compiler/Dialect/HAL/Target/VMLA/VMLATarget.cpp
@@ -48,6 +48,7 @@ VMLATargetBackend(VMLATargetOptions options) : options_(std::move(options)) {} std::string name() const override { return "vmla"; } + std::string filter_pattern() const override { return "vmla"; } void getDependentDialects(DialectRegistry ®istry) const override { registry.insert<VM::VMDialect, VMLA::VMLADialect>();
diff --git a/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp b/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp index 2161067..6851ec2 100644 --- a/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp +++ b/iree/compiler/Dialect/HAL/Target/VulkanSPIRV/VulkanSPIRVTarget.cpp
@@ -233,8 +233,9 @@ VulkanSPIRVTargetBackend(VulkanSPIRVTargetOptions options) : options_(std::move(options)) {} - // NOTE: we could vary this based on the options such as 'vulkan-v1.1'. - std::string name() const override { return "vulkan*"; } + // NOTE: we could vary these based on the options such as 'vulkan-v1.1'. + std::string name() const override { return "vulkan_spirv"; } + std::string filter_pattern() const override { return "vulkan*"; } void getDependentDialects(DialectRegistry ®istry) const override { // clang-format off @@ -252,8 +253,7 @@ IREE::HAL::ExecutableOp executableOp) override { OpBuilder targetBuilder(&executableOp.getBlock().back()); auto targetOp = targetBuilder.create<IREE::HAL::ExecutableTargetOp>( - sourceOp.getLoc(), /*name=*/"vulkan_any", - /*targetBackendFilter=*/name()); + sourceOp.getLoc(), name(), filter_pattern()); OpBuilder containerBuilder(&targetOp.getBlock().back()); auto innerModuleOp = containerBuilder.create<ModuleOp>(sourceOp.getLoc()); @@ -281,7 +281,8 @@ IREE::HAL::ExecutableOp executableOp = dispatchState.executableOp; for (auto executableTargetOp : executableOp.getBlock().getOps<IREE::HAL::ExecutableTargetOp>()) { - if (matchPattern(executableTargetOp.target_backend_filter(), name())) { + if (matchPattern(executableTargetOp.target_backend_filter(), + filter_pattern())) { ModuleOp innerModuleOp = executableTargetOp.getInnerModule(); auto spvModuleOps = innerModuleOp.getOps<spirv::ModuleOp>(); assert(llvm::hasSingleElement(spvModuleOps)); @@ -324,7 +325,7 @@ } auto *region = switchBuilder.addConditionRegion( - IREE::HAL::DeviceMatchIDAttr::get(name(), loc.getContext()), + IREE::HAL::DeviceMatchIDAttr::get(filter_pattern(), loc.getContext()), { dispatchState.workload, dispatchState.commandBuffer, @@ -410,7 +411,8 @@ spirv::ModuleOp spvModuleOp; for (auto executableTargetOp : executableOp.getBlock().getOps<IREE::HAL::ExecutableTargetOp>()) { - if (matchPattern(executableTargetOp.target_backend_filter(), name())) { + if (matchPattern(executableTargetOp.target_backend_filter(), + filter_pattern())) { ModuleOp innerModuleOp = executableTargetOp.getInnerModule(); assert(!innerModuleOp.getAttr( iree_compiler::getEntryPointScheduleAttrName()));
diff --git a/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp b/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp index 1c1c9e4..94b567d 100644 --- a/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp +++ b/iree/compiler/Dialect/HAL/Transforms/MaterializeResourceCaches.cpp
@@ -65,7 +65,7 @@ // loads from variables. for (auto funcOp : moduleOp.getOps<FuncOp>()) { for (auto &block : funcOp) { - for (auto &op : llvm::make_early_inc_range(block)) { + block.walk([&](Operation *op) { if (auto lookupOp = dyn_cast<DescriptorSetLayoutLookupOp>(op)) { replaceDescriptorSetLayoutLookupOp(lookupOp); } else if (auto lookupOp = dyn_cast<ExecutableLayoutLookupOp>(op)) { @@ -73,7 +73,7 @@ } else if (auto lookupOp = dyn_cast<ExecutableLookupOp>(op)) { replaceExecutableLookupOp(lookupOp); } - } + }); } }
diff --git a/iree/compiler/Dialect/HAL/Transforms/Passes.cpp b/iree/compiler/Dialect/HAL/Transforms/Passes.cpp index d17f726..1a9440d 100644 --- a/iree/compiler/Dialect/HAL/Transforms/Passes.cpp +++ b/iree/compiler/Dialect/HAL/Transforms/Passes.cpp
@@ -75,6 +75,13 @@ // been expanded to primitives. passManager.addPass(createPublicABIGenerationPass()); + // Resolve entry point ordinals from nested symbol references prior to + // serialization. As this pass creates lookup ops it should run before + // MaterializeResourceCachesPass. + passManager.addPass(createResolveEntryPointOrdinalsPass()); + passManager.addNestedPass<FuncOp>(createCanonicalizerPass()); + passManager.addNestedPass<FuncOp>(createCSEPass()); + // Gather cachable resources such as executables and descriptor sets and // cache them at initialization-time. passManager.addPass(createMaterializeResourceCachesPass(targetOptions)); @@ -86,10 +93,6 @@ passManager.addNestedPass<FuncOp>(createCanonicalizerPass()); passManager.addNestedPass<FuncOp>(createCSEPass()); - // Resolve entry point ordinals from nested symbol references prior to - // serialization. - passManager.addPass(createResolveEntryPointOrdinalsPass()); - // TODO(#1036): run this once per hal.executable.target in a nested pass // manager so that we have as many passes as hal.executable.target ops. if (transformOptions.serializeExecutables) {
diff --git a/iree/compiler/Dialect/HAL/Transforms/ResolveEntryPointOrdinals.cpp b/iree/compiler/Dialect/HAL/Transforms/ResolveEntryPointOrdinals.cpp index 51f07da..03030d2 100644 --- a/iree/compiler/Dialect/HAL/Transforms/ResolveEntryPointOrdinals.cpp +++ b/iree/compiler/Dialect/HAL/Transforms/ResolveEntryPointOrdinals.cpp
@@ -31,8 +31,19 @@ PatternRewriter &rewriter) const override { auto entryPointOp = dyn_cast<IREE::HAL::ExecutableEntryPointOp>( SymbolTable::lookupNearestSymbolFrom(op, op.entry_point())); + + // Lookup the device for our command buffer, then the executable from the + // entry point's nested reference. + auto device = rewriter.createOrFold<IREE::HAL::CommandBufferDeviceOp>( + op.getLoc(), IREE::HAL::DeviceType::get(rewriter.getContext()), + op.command_buffer()); + auto executableOp = dyn_cast<IREE::HAL::ExecutableOp>( + entryPointOp.getParentOp()->getParentOp()); + auto executable = rewriter.createOrFold<IREE::HAL::ExecutableLookupOp>( + op.getLoc(), device, executableOp.sym_name()); + rewriter.replaceOpWithNewOp<IREE::HAL::CommandBufferDispatchOp>( - op, op.command_buffer(), op.executable(), entryPointOp.ordinalAttr(), + op, op.command_buffer(), executable, entryPointOp.ordinalAttr(), op.workgroup_x(), op.workgroup_y(), op.workgroup_z()); return success(); } @@ -49,8 +60,19 @@ PatternRewriter &rewriter) const override { auto entryPointOp = dyn_cast<IREE::HAL::ExecutableEntryPointOp>( SymbolTable::lookupNearestSymbolFrom(op, op.entry_point())); + + // Lookup the device for our command buffer, then the executable from the + // entry point's nested reference. + auto device = rewriter.createOrFold<IREE::HAL::CommandBufferDeviceOp>( + op.getLoc(), IREE::HAL::DeviceType::get(rewriter.getContext()), + op.command_buffer()); + auto executableOp = dyn_cast<IREE::HAL::ExecutableOp>( + entryPointOp.getParentOp()->getParentOp()); + auto executable = rewriter.createOrFold<IREE::HAL::ExecutableLookupOp>( + op.getLoc(), device, executableOp.sym_name()); + rewriter.replaceOpWithNewOp<IREE::HAL::CommandBufferDispatchIndirectOp>( - op, op.command_buffer(), op.executable(), entryPointOp.ordinalAttr(), + op, op.command_buffer(), executable, entryPointOp.ordinalAttr(), op.workgroups_buffer(), op.workgroups_offset()); return success(); }
diff --git a/iree/compiler/Dialect/HAL/Transforms/test/resolve_entry_point_ordinals.mlir b/iree/compiler/Dialect/HAL/Transforms/test/resolve_entry_point_ordinals.mlir index f254b6e..c5855ca 100644 --- a/iree/compiler/Dialect/HAL/Transforms/test/resolve_entry_point_ordinals.mlir +++ b/iree/compiler/Dialect/HAL/Transforms/test/resolve_entry_point_ordinals.mlir
@@ -19,12 +19,13 @@ func @dispatch_with_nested_references() { %cmd = "test_hal.command_buffer"() : () -> !hal.command_buffer - %exe = "test_hal.executable"() : () -> !hal.executable %x = "test_hal.workgroup_x"() : () -> index %y = "test_hal.workgroup_y"() : () -> index %z = "test_hal.workgroup_z"() : () -> index - // CHECK: hal.command_buffer.dispatch %0, %1, entry_point = 0, workgroup_xyz = [%2, %3, %4] - hal.command_buffer.dispatch.symbol %cmd, %exe, entry_point = @exe::@target::@entry, workgroup_xyz = [%x, %y, %z] + // CHECK: %[[DEVICE:.+]] = hal.command_buffer.device %0 + // CHECK: %[[EXE:.+]] = hal.executable.lookup %[[DEVICE]], @exe + // CHECK: hal.command_buffer.dispatch %0, %[[EXE]], entry_point = 0, workgroup_xyz = [%1, %2, %3] + hal.command_buffer.dispatch.symbol %cmd, @exe::@target::@entry, workgroup_xyz = [%x, %y, %z] return } } @@ -66,11 +67,12 @@ func @dispatch_indirect_with_nested_references() { %cmd = "test_hal.command_buffer"() : () -> !hal.command_buffer - %exe = "test_hal.executable"() : () -> !hal.executable %buffer = "test_hal.buffer"() : () -> !hal.buffer %offset = "test_hal.offset"() : () -> index - // CHECK: hal.command_buffer.dispatch.indirect %0, %1, entry_point = 0, workgroups = %2[%3] - hal.command_buffer.dispatch.indirect.symbol %cmd, %exe, entry_point = @exe::@target::@entry, workgroups = %buffer[%offset] + // CHECK: %[[DEVICE:.+]] = hal.command_buffer.device %0 + // CHECK: %[[EXE:.+]] = hal.executable.lookup %[[DEVICE]], @exe + // CHECK: hal.command_buffer.dispatch.indirect %0, %[[EXE]], entry_point = 0, workgroups = %1[%2] + hal.command_buffer.dispatch.indirect.symbol %cmd, @exe::@target::@entry, workgroups = %buffer[%offset] return } }
diff --git a/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/ConvertStandardToVMLA.cpp b/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/ConvertStandardToVMLA.cpp index 1c790d6..1d4b7e0 100644 --- a/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/ConvertStandardToVMLA.cpp +++ b/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/ConvertStandardToVMLA.cpp
@@ -42,6 +42,15 @@ ConversionPatternRewriter &rewriter) const override { auto value = srcOp.value().dyn_cast<ElementsAttr>(); if (!value) return failure(); + + if (value.getType().getElementType().isInteger(1)) { + value = value.mapValues(rewriter.getIntegerType(8), + llvm::function_ref<APInt(const APInt &val)>( + [](const APInt &val) -> APInt { + return APInt(8, val.getBoolValue()); + })); + } + rewriter.replaceOpWithNewOp<IREE::VMLA::ConstantOp>(srcOp, value); return success(); }
diff --git a/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/test/constant_ops.mlir b/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/test/constant_ops.mlir index 4971289..87307e9 100644 --- a/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/test/constant_ops.mlir +++ b/iree/compiler/Dialect/VMLA/Conversion/StandardToVMLA/test/constant_ops.mlir
@@ -15,3 +15,12 @@ %0 = constant dense<[-1.0, -2.0, 3.0, 4.0]> : tensor<4xf32> return %0 : tensor<4xf32> } + +// ----- + +// CHECK-LABEL: @constant_tensor_bool +func @constant_tensor_bool() -> tensor<4xi1> attributes { sym_visibility = "private" } { + // CHECK: = vmla.constant dense<[0, 1, 1, 0]> : tensor<4xi8> -> !vmla.buffer + %0 = constant dense<[false, true, true, false]> : tensor<4xi1> + return %0 : tensor<4xi1> +}
diff --git a/iree/hal/buffer_test.cc b/iree/hal/buffer_test.cc index 4e627da..bf58539 100644 --- a/iree/hal/buffer_test.cc +++ b/iree/hal/buffer_test.cc
@@ -43,7 +43,7 @@ // We don't currently do any padding on the host. // Other implementations may differ. - EXPECT_GE(14, buffer->allocation_size()); + EXPECT_LE(14, buffer->allocation_size()); EXPECT_EQ(0, buffer->byte_offset()); EXPECT_EQ(14, buffer->byte_length()); @@ -69,7 +69,7 @@ HeapBuffer::AllocateCopy(BufferUsage::kTransfer | BufferUsage::kMapping, src_data.data(), src_data.size()); EXPECT_NE(nullptr, buffer->allocator()); - EXPECT_GE(src_data.size(), buffer->allocation_size()); + EXPECT_LE(src_data.size(), buffer->allocation_size()); // Data should have been copied. std::vector<uint8_t> actual_data(src_data.size()); @@ -99,7 +99,7 @@ EXPECT_NE(nullptr, buffer->allocator()); EXPECT_EQ(MemoryType::kHostLocal, buffer->memory_type()); EXPECT_EQ(BufferUsage::kTransfer | BufferUsage::kMapping, buffer->usage()); - EXPECT_GE(src_data.size() * sizeof(int32_t), buffer->allocation_size()); + EXPECT_LE(src_data.size() * sizeof(int32_t), buffer->allocation_size()); // Data should have been copied. std::vector<int32_t> actual_data(src_data.size());
diff --git a/iree/hal/cts/BUILD b/iree/hal/cts/BUILD index 445e5af..cbee0cf 100644 --- a/iree/hal/cts/BUILD +++ b/iree/hal/cts/BUILD
@@ -56,6 +56,18 @@ ) cc_test( + name = "buffer_test", + srcs = ["buffer_test.cc"], + deps = [ + ":cts_test_base", + "//iree/base:status", + "//iree/hal:driver_registry", + "//iree/testing:gtest", + "//iree/testing:gtest_main", + ], +) + +cc_test( name = "command_buffer_test", srcs = ["command_buffer_test.cc"], deps = [
diff --git a/iree/hal/cts/CMakeLists.txt b/iree/hal/cts/CMakeLists.txt index 3de079d..1271a0c 100644 --- a/iree/hal/cts/CMakeLists.txt +++ b/iree/hal/cts/CMakeLists.txt
@@ -47,6 +47,18 @@ iree_cc_test( NAME + buffer_test + SRCS + "buffer_test.cc" + DEPS + ::cts_test_base + iree::base::status + iree::hal::driver_registry + iree::testing::gtest +) + +iree_cc_test( + NAME command_buffer_test SRCS "command_buffer_test.cc"
diff --git a/iree/hal/cts/allocator_test.cc b/iree/hal/cts/allocator_test.cc index 00d72b4..1608c62 100644 --- a/iree/hal/cts/allocator_test.cc +++ b/iree/hal/cts/allocator_test.cc
@@ -65,6 +65,18 @@ EXPECT_GE(buffer->allocation_size(), allocation_size); // Larger is okay. } +TEST_P(AllocatorTest, CanUseBufferLike) { + MemoryType memory_type = MemoryType::kHostLocal | MemoryType::kDeviceVisible; + BufferUsage usage = BufferUsage::kMapping; + size_t allocation_size = 1024; + + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, allocator_->Allocate(memory_type, usage, allocation_size)); + // Using the buffer for its original requested purpose should be fine. + EXPECT_TRUE( + allocator_->CanUseBufferLike(allocator_, memory_type, usage, usage)); +} + INSTANTIATE_TEST_SUITE_P(AllDrivers, AllocatorTest, ::testing::ValuesIn(DriverRegistry::shared_registry() ->EnumerateAvailableDrivers()),
diff --git a/iree/hal/cts/buffer_test.cc b/iree/hal/cts/buffer_test.cc new file mode 100644 index 0000000..26eecc7 --- /dev/null +++ b/iree/hal/cts/buffer_test.cc
@@ -0,0 +1,385 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "iree/base/status.h" +#include "iree/hal/cts/cts_test_base.h" +#include "iree/hal/driver_registry.h" +#include "iree/testing/gtest.h" +#include "iree/testing/status_matchers.h" + +namespace iree { +namespace hal { +namespace cts { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Eq; + +// Note: this file only covers hal::Buffer APIs that can be overridden by +// subclasses. Errors caught by hal::Buffer's common validations are not +// covered as they are already tested in iree/hal/buffer_test.cc. + +class BufferTest : public CtsTestBase {}; + +TEST_P(BufferTest, Allocate) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 14)); + + EXPECT_NE(nullptr, buffer->allocator()); + EXPECT_EQ(MemoryAccess::kAll, buffer->allowed_access()); + EXPECT_EQ(MemoryType::kHostLocal | MemoryType::kDeviceVisible, + buffer->memory_type()); + EXPECT_EQ(BufferUsage::kTransfer | BufferUsage::kMapping, buffer->usage()); + + EXPECT_LE(14, buffer->allocation_size()); + EXPECT_EQ(0, buffer->byte_offset()); + EXPECT_EQ(14, buffer->byte_length()); +} + +TEST_P(BufferTest, AllocateZeroLength) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 0)); + EXPECT_LE(0, buffer->allocation_size()); +} + +TEST_P(BufferTest, Fill8) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 5)); + + std::vector<uint8_t> actual_data(buffer->allocation_size()); + + // Fill with a sentinel. + IREE_EXPECT_OK(buffer->Fill8(0, buffer->allocation_size(), 0x33u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x33, 0x33, 0x33, 0x33, 0x33)); + + // Zero fills are fine. + IREE_EXPECT_OK(buffer->Fill8(0, 0, 0x44u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x33, 0x33, 0x33, 0x33, 0x33)); + + // Fill the remaining parts of the buffer by using kWholeBuffer. + IREE_EXPECT_OK(buffer->Fill8(2, kWholeBuffer, 0x55u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x33, 0x33, 0x55, 0x55, 0x55)); + + // Fill a small region of the buffer. + IREE_EXPECT_OK(buffer->Fill8(1, 1, 0x66u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x33, 0x66, 0x55, 0x55, 0x55)); + + // Whole buffer helper. + IREE_EXPECT_OK(buffer->Fill8(0x99u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x99, 0x99, 0x99, 0x99, 0x99)); +} + +TEST_P(BufferTest, Fill16) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 9)); + + std::vector<uint8_t> actual_data(buffer->allocation_size()); + + // Fill with a sentinel. + IREE_EXPECT_OK(buffer->Fill16(0, 4, 0x1122u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x22, 0x11, 0x22, 0x11, 0, 0, 0, 0, 0)); + + // Zero fills are fine. + IREE_EXPECT_OK(buffer->Fill16(0, 0, 0x5566u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0x22, 0x11, 0x22, 0x11, 0, 0, 0, 0, 0)); + + // Fill the remaining parts of the buffer by using kWholeBuffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto aligned_buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 8)); + IREE_EXPECT_OK(aligned_buffer->Fill16(4, kWholeBuffer, 0x5566u)); + std::vector<uint8_t> aligned_actual_data(aligned_buffer->allocation_size()); + IREE_EXPECT_OK(aligned_buffer->ReadData(0, aligned_actual_data.data(), + aligned_actual_data.size())); + EXPECT_THAT(aligned_actual_data, + ElementsAre(0, 0, 0, 0, 0x66, 0x55, 0x66, 0x55)); + + // Whole buffer helper. + IREE_EXPECT_OK(aligned_buffer->Fill16(0x5566u)); + IREE_EXPECT_OK(aligned_buffer->ReadData(0, aligned_actual_data.data(), + aligned_actual_data.size())); + EXPECT_THAT(aligned_actual_data, + ElementsAre(0x66, 0x55, 0x66, 0x55, 0x66, 0x55, 0x66, 0x55)); +} + +TEST_P(BufferTest, Fill32) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 9)); + + std::vector<uint8_t> actual_data(buffer->allocation_size()); + + // Fill with a sentinel. + IREE_EXPECT_OK(buffer->Fill32(0, 8, 0x11223344u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, + ElementsAre(0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11, 0)); + + // Zero fills are fine. + IREE_EXPECT_OK(buffer->Fill32(0, 0, 0x55667788u)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, + ElementsAre(0x44, 0x33, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11, 0)); + + // Fill the remaining parts of the buffer by using kWholeBuffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto aligned_buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 8)); + IREE_EXPECT_OK(aligned_buffer->Fill32(4, kWholeBuffer, 0x55667788u)); + std::vector<uint8_t> aligned_actual_data(aligned_buffer->allocation_size()); + IREE_EXPECT_OK(aligned_buffer->ReadData(0, aligned_actual_data.data(), + aligned_actual_data.size())); + EXPECT_THAT(aligned_actual_data, + ElementsAre(0, 0, 0, 0, 0x88, 0x77, 0x66, 0x55)); + + // Whole buffer helper. + IREE_EXPECT_OK(aligned_buffer->Fill32(0x55667788u)); + IREE_EXPECT_OK(aligned_buffer->ReadData(0, aligned_actual_data.data(), + aligned_actual_data.size())); + EXPECT_THAT(aligned_actual_data, + ElementsAre(0x88, 0x77, 0x66, 0x55, 0x88, 0x77, 0x66, 0x55)); +} + +TEST_P(BufferTest, ReadWriteData) { + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, 4)); + + std::vector<uint8_t> actual_data(4); + + // Write over the entire buffer. + std::vector<uint8_t> new_data = {10, 20, 30, 40}; + IREE_EXPECT_OK(buffer->WriteData(0, new_data.data(), new_data.size())); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, Eq(new_data)); + + // Writing zero bytes is valid. + std::vector<uint8_t> zero_data; + IREE_EXPECT_OK(buffer->WriteData(0, zero_data.data(), 0)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, Eq(new_data)); + + // Write over a portion of the buffer. + std::vector<uint8_t> partial_data = {99}; + IREE_EXPECT_OK( + buffer->WriteData(1, partial_data.data(), partial_data.size())); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(10, 99, 30, 40)); +} + +TEST_P(BufferTest, CopyData) { + std::vector<uint8_t> src_data = {0, 1, 2, 3}; + IREE_ASSERT_OK_AND_ASSIGN( + auto src_buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(src_buffer->WriteData(0, src_data.data(), src_data.size())); + + std::vector<uint8_t> dst_data = {0, 1, 2, 3, 4}; + IREE_ASSERT_OK_AND_ASSIGN( + auto dst_buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, dst_data.size())); + IREE_EXPECT_OK(dst_buffer->WriteData(0, dst_data.data(), dst_data.size())); + + // Copy of length 0 should not change the dest buffer. + IREE_EXPECT_OK(dst_buffer->CopyData(0, src_buffer.get(), 0, 0)); + std::vector<uint8_t> actual_data(dst_data.size()); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, Eq(dst_data)); + + // Copy a subrange of the buffer. + IREE_EXPECT_OK(dst_buffer->CopyData(1, src_buffer.get(), 2, 2)); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0, 2, 3, 3, 4)); + + // Copy the entire buffer using kWholeBuffer. This will adjust sizes + // to ensure that the min buffer is taken. We test both src and dst buffer + // offset/length calculations (note that some may end up as 0 copies). + IREE_EXPECT_OK(dst_buffer->CopyData(3, src_buffer.get(), 0, kWholeBuffer)); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0, 2, 3, 0, 1)); + IREE_EXPECT_OK(dst_buffer->CopyData(0, src_buffer.get(), 2, kWholeBuffer)); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(2, 3, 3, 0, 1)); + IREE_EXPECT_OK(dst_buffer->CopyData(0, src_buffer.get(), 3, kWholeBuffer)); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(3, 3, 3, 0, 1)); + IREE_EXPECT_OK(dst_buffer->CopyData(4, src_buffer.get(), 0, kWholeBuffer)); + IREE_EXPECT_OK( + dst_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(3, 3, 3, 0, 0)); +} + +TEST_P(BufferTest, MapMemory) { + std::vector<uint8_t> src_data = {0, 1, 2, 3, 4, 5, 6}; + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(buffer->WriteData(0, src_data.data(), src_data.size())); + + // 0-length mappings are valid. + IREE_ASSERT_OK_AND_ASSIGN( + auto mapping, buffer->MapMemory<uint8_t>(MemoryAccess::kRead, 0, 0)); + EXPECT_TRUE(mapping.empty()); + EXPECT_EQ(0, mapping.size()); + EXPECT_EQ(0, mapping.byte_length()); + EXPECT_NE(nullptr, mapping.data()); + IREE_ASSERT_OK_AND_ASSIGN(auto span, mapping.Subspan()); + EXPECT_TRUE(span.empty()); + mapping.reset(); + + // Map the whole buffer for reading. + IREE_ASSERT_OK_AND_ASSIGN(mapping, buffer->MapMemory<uint8_t>( + MemoryAccess::kRead, 0, kWholeBuffer)); + EXPECT_EQ(src_data.size(), mapping.size()); + IREE_ASSERT_OK_AND_ASSIGN(span, mapping.Subspan()); + EXPECT_THAT(span, ElementsAre(0, 1, 2, 3, 4, 5, 6)); + mapping.reset(); + + // Map a portion of the buffer for reading. + IREE_ASSERT_OK_AND_ASSIGN( + mapping, buffer->MapMemory<uint8_t>(MemoryAccess::kRead, 1, 2)); + EXPECT_EQ(2, mapping.size()); + IREE_ASSERT_OK_AND_ASSIGN(span, mapping.Subspan()); + EXPECT_THAT(span, ElementsAre(1, 2)); + mapping.reset(); +} + +TEST_P(BufferTest, MapMemoryNonByte) { + std::vector<uint8_t> src_data = {0, 1, 2, 3, 4, 5, 6}; + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(buffer->WriteData(0, src_data.data(), src_data.size())); + + // Map the buffer as non-byte values. + // Note that we'll round down to the number of valid elements at the + // alignment. + IREE_ASSERT_OK_AND_ASSIGN(auto mapping16, + buffer->MapMemory<uint16_t>(MemoryAccess::kRead)); + EXPECT_EQ(3, mapping16.size()); + EXPECT_LE(6, mapping16.byte_length()); + IREE_ASSERT_OK_AND_ASSIGN(auto span16, mapping16.Subspan()); + EXPECT_THAT(span16, ElementsAre(0x0100, 0x0302, 0x0504)); + mapping16.reset(); +} + +TEST_P(BufferTest, MapMemoryWrite) { + std::vector<uint8_t> src_data = {0, 1, 2, 3, 4, 5, 6}; + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(buffer->WriteData(0, src_data.data(), src_data.size())); + + // Map and modify the data. We should see it when we read back. + IREE_ASSERT_OK_AND_ASSIGN( + auto mapping, buffer->MapMemory<uint8_t>(MemoryAccess::kWrite, 1, 2)); + auto mutable_data = mapping.mutable_data(); + mutable_data[0] = 0xAA; + mutable_data[1] = 0xBB; + mapping.reset(); + std::vector<uint8_t> actual_data(src_data.size()); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0, 0xAA, 0xBB, 3, 4, 5, 6)); +} + +TEST_P(BufferTest, MapMemoryDiscard) { + std::vector<uint8_t> src_data = {0, 1, 2, 3, 4, 5, 6}; + IREE_ASSERT_OK_AND_ASSIGN( + auto buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(buffer->WriteData(0, src_data.data(), src_data.size())); + + // Map for discard. Note that we can't really rely on the value of the data + // so we just trust that it's been discarded. It's a hint, anyway. We can be + // sure that the data we didn't want to discard is the same though. + std::vector<uint8_t> actual_data(src_data.size()); + IREE_ASSERT_OK_AND_ASSIGN( + auto mapping, + buffer->MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, 1, 2)); + IREE_EXPECT_OK(buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0, _, _, 3, 4, 5, 6)); + mapping.reset(); +} + +TEST_P(BufferTest, MapMemorySubspan) { + std::vector<uint8_t> src_data = {0, 1, 2, 3, 4, 5, 6}; + IREE_ASSERT_OK_AND_ASSIGN( + auto parent_buffer, + device_->allocator()->Allocate( + MemoryType::kHostLocal | MemoryType::kDeviceVisible, + BufferUsage::kTransfer | BufferUsage::kMapping, src_data.size())); + IREE_EXPECT_OK(parent_buffer->WriteData(0, src_data.data(), src_data.size())); + + IREE_ASSERT_OK_AND_ASSIGN(auto subspan_buffer, + Buffer::Subspan(parent_buffer, 1, 3)); + IREE_ASSERT_OK_AND_ASSIGN( + auto mapping, + subspan_buffer->MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, 1, 2)); + auto* mutable_data = mapping.mutable_data(); + mutable_data[0] = 0xCC; + mutable_data[1] = 0xDD; + mapping.reset(); + + std::vector<uint8_t> actual_data(src_data.size()); + IREE_EXPECT_OK( + parent_buffer->ReadData(0, actual_data.data(), actual_data.size())); + EXPECT_THAT(actual_data, ElementsAre(0, 1, 0xCC, 0xDD, 4, 5, 6)); +} + +INSTANTIATE_TEST_SUITE_P(AllDrivers, BufferTest, + ::testing::ValuesIn(DriverRegistry::shared_registry() + ->EnumerateAvailableDrivers()), + GenerateTestName()); + +} // namespace cts +} // namespace hal +} // namespace iree
diff --git a/iree/hal/cts/command_buffer_test.cc b/iree/hal/cts/command_buffer_test.cc index c146d89..5b6e4d6 100644 --- a/iree/hal/cts/command_buffer_test.cc +++ b/iree/hal/cts/command_buffer_test.cc
@@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <cstring> +#include <vector> + #include "iree/base/status.h" #include "iree/hal/cts/cts_test_base.h" #include "iree/hal/driver_registry.h" @@ -22,9 +25,24 @@ namespace hal { namespace cts { -class CommandBufferTest : public CtsTestBase {}; +using ::testing::ContainerEq; -TEST_P(CommandBufferTest, CreateCommandBuffer) { +class CommandBufferTest : public CtsTestBase { + protected: + static constexpr device_size_t kBufferNumBytes = 16; + + void SubmitAndWait(CommandQueue* command_queue, + CommandBuffer* command_buffer) { + IREE_ASSERT_OK_AND_ASSIGN(auto signal_semaphore, + device_->CreateSemaphore(0ull)); + + IREE_ASSERT_OK(command_queue->Submit( + {{}, {command_buffer}, {{signal_semaphore.get(), 1ull}}})); + IREE_ASSERT_OK(signal_semaphore->Wait(1ull, InfiniteFuture())); + } +}; + +TEST_P(CommandBufferTest, Create) { IREE_ASSERT_OK_AND_ASSIGN( auto command_buffer, device_->CreateCommandBuffer(CommandBufferMode::kOneShot, @@ -37,7 +55,187 @@ EXPECT_FALSE(command_buffer->is_recording()); } -// TODO(scotttodd): Begin, End, UpdateBuffer, CopyBuffer, Dispatch, Sync, etc. +TEST_P(CommandBufferTest, BeginEnd) { + IREE_ASSERT_OK_AND_ASSIGN( + auto command_buffer, + device_->CreateCommandBuffer(CommandBufferMode::kOneShot, + CommandCategory::kDispatch)); + + EXPECT_FALSE(command_buffer->is_recording()); + IREE_EXPECT_OK(command_buffer->Begin()); + EXPECT_TRUE(command_buffer->is_recording()); + IREE_EXPECT_OK(command_buffer->End()); + EXPECT_FALSE(command_buffer->is_recording()); +} + +TEST_P(CommandBufferTest, FillBufferWithRepeatedBytes) { + IREE_ASSERT_OK_AND_ASSIGN( + auto command_buffer, + device_->CreateCommandBuffer(CommandBufferMode::kOneShot, + CommandCategory::kTransfer)); + + IREE_ASSERT_OK_AND_ASSIGN( + auto device_buffer, + device_->allocator()->Allocate( + MemoryType::kDeviceLocal | MemoryType::kHostVisible, + BufferUsage::kAll, kBufferNumBytes)); + + std::vector<uint8_t> reference_buffer(kBufferNumBytes); + + IREE_EXPECT_OK(command_buffer->Begin()); + + // Fill the device buffer with segments of different values so that we can + // test both fill and offset/size. + + uint8_t val1 = 0x07; + IREE_EXPECT_OK(command_buffer->FillBuffer(device_buffer.get(), + /*target_offset=*/0, + /*length=*/kBufferNumBytes / 4, + &val1, + /*pattern_length=*/1)); + std::memset(reference_buffer.data(), val1, kBufferNumBytes / 4); + + uint8_t val2 = 0xbe; + IREE_EXPECT_OK( + command_buffer->FillBuffer(device_buffer.get(), + /*target_offset=*/kBufferNumBytes / 4, + /*length=*/kBufferNumBytes / 4, &val2, + /*pattern_length=*/1)); + std::memset(reference_buffer.data() + kBufferNumBytes / 4, val2, + kBufferNumBytes / 4); + + uint8_t val3 = 0x54; + IREE_EXPECT_OK( + command_buffer->FillBuffer(device_buffer.get(), + /*target_offset=*/kBufferNumBytes / 2, + /*length=*/kBufferNumBytes / 2, &val3, + /*pattern_length=*/1)); + std::memset(reference_buffer.data() + kBufferNumBytes / 2, val3, + kBufferNumBytes / 2); + + IREE_EXPECT_OK(command_buffer->End()); + + SubmitAndWait(device_->transfer_queues()[0], command_buffer.get()); + + // Read back the device buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto mapped_memory, + device_buffer->MapMemory<uint8_t>(MemoryAccess::kRead)); + IREE_EXPECT_OK(mapped_memory.Invalidate()); + + std::vector<uint8_t> actual_data(mapped_memory.data(), + mapped_memory.data() + kBufferNumBytes); + EXPECT_THAT(actual_data, ContainerEq(reference_buffer)); +} + +TEST_P(CommandBufferTest, CopyWholeBuffer) { + IREE_ASSERT_OK_AND_ASSIGN( + auto command_buffer, + device_->CreateCommandBuffer(CommandBufferMode::kOneShot, + CommandCategory::kTransfer)); + + // Create a host buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto host_buffer, device_->allocator()->Allocate( + MemoryType::kHostVisible | MemoryType::kHostCached | + MemoryType::kDeviceVisible, + BufferUsage::kAll, kBufferNumBytes)); + + // Fill the host buffer. + uint8_t i8_val = 0x55; + IREE_EXPECT_OK(host_buffer->Fill8(0, kWholeBuffer, i8_val)); + IREE_ASSERT_OK_AND_ASSIGN( + auto host_mapped_memory, + // Cannot use kDiscard here given we filled in the above. + host_buffer->MapMemory<uint8_t>(MemoryAccess::kWrite)); + IREE_EXPECT_OK(host_mapped_memory.Flush()); + + std::vector<uint8_t> reference_buffer(kBufferNumBytes); + std::memset(reference_buffer.data(), i8_val, kBufferNumBytes); + + // Create a device buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto device_buffer, + device_->allocator()->Allocate( + MemoryType::kDeviceLocal | MemoryType::kHostVisible, + BufferUsage::kAll, kBufferNumBytes)); + + // Copy the host buffer to the device buffer. + IREE_EXPECT_OK(command_buffer->Begin()); + IREE_EXPECT_OK( + command_buffer->CopyBuffer(host_buffer.get(), /*source_offset=*/0, + device_buffer.get(), /*target_offset=*/0, + /*length=*/kBufferNumBytes)); + IREE_EXPECT_OK(command_buffer->End()); + + SubmitAndWait(device_->transfer_queues()[0], command_buffer.get()); + + // Read back the device buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto device_mapped_memory, + device_buffer->MapMemory<uint8_t>(MemoryAccess::kRead)); + IREE_EXPECT_OK(device_mapped_memory.Invalidate()); + + std::vector<uint8_t> actual_data( + device_mapped_memory.data(), + device_mapped_memory.data() + kBufferNumBytes); + EXPECT_THAT(actual_data, ContainerEq(reference_buffer)); +} + +TEST_P(CommandBufferTest, CopySubBuffer) { + IREE_ASSERT_OK_AND_ASSIGN( + auto command_buffer, + device_->CreateCommandBuffer(CommandBufferMode::kOneShot, + CommandCategory::kTransfer)); + // Create a device buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto device_buffer, + device_->allocator()->Allocate( + MemoryType::kDeviceLocal | MemoryType::kHostVisible, + BufferUsage::kAll, kBufferNumBytes)); + + // Create another host buffer with a smaller size. + IREE_ASSERT_OK_AND_ASSIGN( + auto host_buffer, device_->allocator()->Allocate( + MemoryType::kHostVisible | MemoryType::kHostCached | + MemoryType::kDeviceVisible, + BufferUsage::kAll, kBufferNumBytes / 2)); + + // Fill the host buffer. + uint8_t i8_val = 0x88; + IREE_EXPECT_OK(host_buffer->Fill8(0, kWholeBuffer, i8_val)); + IREE_ASSERT_OK_AND_ASSIGN( + auto host_mapped_memory, + // Cannot use kDiscard here given we filled in the above. + host_buffer->MapMemory<uint8_t>(MemoryAccess::kWrite)); + IREE_EXPECT_OK(host_mapped_memory.Flush()); + + std::vector<uint8_t> reference_buffer(kBufferNumBytes); + std::memset(reference_buffer.data() + 8, i8_val, kBufferNumBytes / 2 - 4); + + // Copy the host buffer to the device buffer. + IREE_EXPECT_OK(command_buffer->Begin()); + IREE_EXPECT_OK( + command_buffer->CopyBuffer(host_buffer.get(), /*source_offset=*/4, + device_buffer.get(), /*target_offset=*/8, + /*length=*/kBufferNumBytes / 2 - 4)); + IREE_EXPECT_OK(command_buffer->End()); + + SubmitAndWait(device_->transfer_queues()[0], command_buffer.get()); + + // Read back the device buffer. + IREE_ASSERT_OK_AND_ASSIGN( + auto device_mapped_memory, + device_buffer->MapMemory<uint8_t>(MemoryAccess::kRead)); + IREE_EXPECT_OK(device_mapped_memory.Invalidate()); + + std::vector<uint8_t> actual_data( + device_mapped_memory.data(), + device_mapped_memory.data() + kBufferNumBytes); + EXPECT_THAT(actual_data, ContainerEq(reference_buffer)); +} + +// TODO(scotttodd): UpdateBuffer, Dispatch, Sync, etc. INSTANTIATE_TEST_SUITE_P(AllDrivers, CommandBufferTest, ::testing::ValuesIn(DriverRegistry::shared_registry()
diff --git a/iree/hal/metal/CMakeLists.txt b/iree/hal/metal/CMakeLists.txt index a7e643d..0f49362 100644 --- a/iree/hal/metal/CMakeLists.txt +++ b/iree/hal/metal/CMakeLists.txt
@@ -61,6 +61,7 @@ DEPS ::metal_command_buffer ::metal_command_queue + ::metal_direct_allocator ::metal_shared_event absl::strings absl::span @@ -79,6 +80,26 @@ iree_cc_library( NAME + metal_direct_allocator + HDRS + "metal_buffer.h" + "metal_direct_allocator.h" + SRCS + "metal_buffer.mm" + "metal_direct_allocator.mm" + DEPS + absl::memory + iree::base::logging + iree::base::status + iree::base::tracing + iree::hal::allocator + LINKOPTS + "-framework Metal" + PUBLIC +) + +iree_cc_library( + NAME metal_driver HDRS "metal_driver.h"
diff --git a/iree/hal/metal/metal_buffer.h b/iree/hal/metal/metal_buffer.h new file mode 100644 index 0000000..dd62e8e --- /dev/null +++ b/iree/hal/metal/metal_buffer.h
@@ -0,0 +1,103 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 IREE_HAL_METAL_METAL_BUFFER_H_ +#define IREE_HAL_METAL_METAL_BUFFER_H_ + +#import <Metal/Metal.h> + +#include "iree/hal/buffer.h" + +namespace iree { +namespace hal { +namespace metal { + +class MetalDirectAllocator; + +// A buffer implementation for Metal that directly wraps a MTLBuffer. +class MetalBuffer final : public Buffer { + public: + // Creates a MetalBuffer instance with retaining the given id<MTLBuffer>. + static StatusOr<ref_ptr<MetalBuffer>> Create( + MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, + device_size_t allocation_size, device_size_t byte_offset, + device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue); + + // Creates a MetalBuffer instance without retaining the given id<MTLBuffer>. + static StatusOr<ref_ptr<MetalBuffer>> CreateUnretained( + MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, + device_size_t allocation_size, device_size_t byte_offset, + device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue); + + ~MetalBuffer() override; + + id<MTLBuffer> handle() const { return metal_handle_; } + + private: + // Creates a MetalBuffer instance without retaining the given id<MTLBuffer>. + MetalBuffer(MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, + device_size_t allocation_size, device_size_t byte_offset, + device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue); + + Status FillImpl(device_size_t byte_offset, device_size_t byte_length, + const void* pattern, device_size_t pattern_length) override; + Status ReadDataImpl(device_size_t source_offset, void* data, + device_size_t data_length) override; + Status WriteDataImpl(device_size_t target_offset, const void* data, + device_size_t data_length) override; + Status CopyDataImpl(device_size_t target_offset, Buffer* source_buffer, + device_size_t source_offset, + device_size_t data_length) override; + + Status MapMemoryImpl(MappingMode mapping_mode, + MemoryAccessBitfield memory_access, + device_size_t local_byte_offset, + device_size_t local_byte_length, + void** out_data) override; + Status UnmapMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length, void* data) override; + Status InvalidateMappedMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length) override; + Status FlushMappedMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length) override; + + // Returns true if we need to automatically invaliate/flush CPU caches to keep + // memory hierarchy consistent. + // + // Note: this is needed when the buffer is requested with + // MemoryType::kHostCoherent bit but under the hood we are using memory types + // that does not have that property natively, e.g., MTLStorageModeManaged. + // Under such circumstances, we need to perform the invalidate/flush operation + // "automatically" for users. + bool requires_autosync() const; + + // We need to hold an reference to the queue so that we can encode + // synchronizeResource commands for synchronizing the buffer with + // MTLResourceStorageModeManaged. + id<MTLCommandQueue> metal_transfer_queue_; + + id<MTLBuffer> metal_handle_; +}; + +} // namespace metal +} // namespace hal +} // namespace iree + +#endif // IREE_HAL_METAL_METAL_BUFFER_H_
diff --git a/iree/hal/metal/metal_buffer.mm b/iree/hal/metal/metal_buffer.mm new file mode 100644 index 0000000..78e7e98 --- /dev/null +++ b/iree/hal/metal/metal_buffer.mm
@@ -0,0 +1,203 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "iree/hal/metal/metal_buffer.h" + +#include "iree/base/status.h" +#include "iree/base/tracing.h" +#include "iree/hal/metal/metal_direct_allocator.h" + +namespace iree { +namespace hal { +namespace metal { + +// static +StatusOr<ref_ptr<MetalBuffer>> MetalBuffer::Create( + MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, device_size_t allocation_size, + device_size_t byte_offset, device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue) { + IREE_TRACE_SCOPE0("MetalBuffer::Create"); + return assign_ref(new MetalBuffer(allocator, memory_type, allowed_access, usage, allocation_size, + byte_offset, byte_length, [buffer retain], transfer_queue)); +} + +// static +StatusOr<ref_ptr<MetalBuffer>> MetalBuffer::CreateUnretained( + MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, device_size_t allocation_size, + device_size_t byte_offset, device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue) { + IREE_TRACE_SCOPE0("MetalBuffer::Create"); + return assign_ref(new MetalBuffer(allocator, memory_type, allowed_access, usage, allocation_size, + byte_offset, byte_length, buffer, transfer_queue)); +} + +MetalBuffer::MetalBuffer(MetalDirectAllocator* allocator, MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, BufferUsageBitfield usage, + device_size_t allocation_size, device_size_t byte_offset, + device_size_t byte_length, id<MTLBuffer> buffer, + id<MTLCommandQueue> transfer_queue) + : Buffer(allocator, memory_type, allowed_access, usage, allocation_size, byte_offset, + byte_length), + metal_transfer_queue_([transfer_queue retain]), + metal_handle_(buffer) {} + +MetalBuffer::~MetalBuffer() { + IREE_TRACE_SCOPE0("MetalBuffer::dtor"); + [metal_handle_ release]; + [metal_transfer_queue_ release]; +} + +Status MetalBuffer::FillImpl(device_size_t byte_offset, device_size_t byte_length, + const void* pattern, device_size_t pattern_length) { + IREE_ASSIGN_OR_RETURN(auto mapping, + MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, byte_offset, byte_length)); + void* data_ptr = static_cast<void*>(mapping.mutable_data()); + switch (pattern_length) { + case 1: { + uint8_t* data = static_cast<uint8_t*>(data_ptr); + uint8_t value_bits = *static_cast<const uint8_t*>(pattern); + std::fill_n(data, byte_length, value_bits); + break; + } + case 2: { + uint16_t* data = static_cast<uint16_t*>(data_ptr); + uint16_t value_bits = *static_cast<const uint16_t*>(pattern); + std::fill_n(data, byte_length / sizeof(uint16_t), value_bits); + break; + } + case 4: { + uint32_t* data = static_cast<uint32_t*>(data_ptr); + uint32_t value_bits = *static_cast<const uint32_t*>(pattern); + std::fill_n(data, byte_length / sizeof(uint32_t), value_bits); + break; + } + default: + return InvalidArgumentErrorBuilder(IREE_LOC) + << "Unsupported scalar data size: " << pattern_length; + } + return OkStatus(); +} + +Status MetalBuffer::ReadDataImpl(device_size_t source_offset, void* data, + device_size_t data_length) { + IREE_ASSIGN_OR_RETURN(auto mapping, + MapMemory<uint8_t>(MemoryAccess::kRead, source_offset, data_length)); + std::memcpy(data, mapping.data(), mapping.byte_length()); + return OkStatus(); +} + +Status MetalBuffer::WriteDataImpl(device_size_t target_offset, const void* data, + device_size_t data_length) { + IREE_ASSIGN_OR_RETURN( + auto mapping, MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, target_offset, data_length)); + std::memcpy(mapping.mutable_data(), data, mapping.byte_length()); + return OkStatus(); +} + +Status MetalBuffer::CopyDataImpl(device_size_t target_offset, Buffer* source_buffer, + device_size_t source_offset, device_size_t data_length) { + // This is pretty terrible. Let's not do this. + // TODO(benvanik): a way for allocators to indicate transfer compat. + IREE_ASSIGN_OR_RETURN(auto source_mapping, source_buffer->MapMemory<uint8_t>( + MemoryAccess::kRead, source_offset, data_length)); + CHECK_EQ(data_length, source_mapping.size()); + IREE_ASSIGN_OR_RETURN(auto target_mapping, MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, + target_offset, data_length)); + CHECK_EQ(data_length, target_mapping.size()); + std::memcpy(target_mapping.mutable_data(), source_mapping.data(), data_length); + return OkStatus(); +} + +Status MetalBuffer::MapMemoryImpl(MappingMode mapping_mode, MemoryAccessBitfield memory_access, + device_size_t local_byte_offset, device_size_t local_byte_length, + void** out_data) { + uint8_t* data_ptr = reinterpret_cast<uint8_t*>([metal_handle_ contents]); + *out_data = data_ptr + local_byte_offset; + + // If we mapped for discard scribble over the bytes. This is not a mandated + // behavior but it will make debugging issues easier. Alternatively for + // heap buffers we could reallocate them such that ASAN yells, but that + // would only work if the entire buffer was discarded. +#ifndef NDEBUG + if (AnyBitSet(memory_access & MemoryAccess::kDiscard)) { + std::memset(data_ptr + local_byte_offset, 0xCD, local_byte_length); + } +#endif // !NDEBUG + + if (requires_autosync()) { + IREE_RETURN_IF_ERROR(InvalidateMappedMemoryImpl(local_byte_offset, local_byte_length)); + } + + return OkStatus(); +} + +Status MetalBuffer::UnmapMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length, void* data) { + if (requires_autosync()) { + IREE_RETURN_IF_ERROR(FlushMappedMemoryImpl(local_byte_offset, local_byte_length)); + } + + return OkStatus(); +} + +Status MetalBuffer::InvalidateMappedMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length) { +#ifdef IREE_PLATFORM_MACOS + // The following is only necessary for MTLStorageManaged. + if (metal_handle_.storageMode == MTLStorageModeManaged) { + @autoreleasepool { + id<MTLCommandBuffer> command_buffer = + [metal_transfer_queue_ commandBufferWithUnretainedReferences]; + + id<MTLBlitCommandEncoder> blit_encoder = [command_buffer blitCommandEncoder]; + [blit_encoder synchronizeResource:metal_handle_]; + [blit_encoder endEncoding]; + + [command_buffer commit]; + [command_buffer waitUntilCompleted]; + } + } +#endif + + return OkStatus(); +} + +Status MetalBuffer::FlushMappedMemoryImpl(device_size_t local_byte_offset, + device_size_t local_byte_length) { +#ifdef IREE_PLATFORM_MACOS + // The following is only necessary for MTLStorageManaged. + if (metal_handle_.storageMode == MTLStorageModeManaged) { + [metal_handle_ didModifyRange:NSMakeRange(local_byte_offset, local_byte_length)]; + } +#endif + + return OkStatus(); +} + +bool MetalBuffer::requires_autosync() const { + // We only need to perform "automatic" resource synchronization if it's MTLStorageModeManaged, + // which is only available on macOS. +#ifdef IREE_PLATFORM_MACOS + return AllBitsSet(memory_type(), MemoryType::kHostCoherent) && + metal_handle_.storageMode == MTLStorageModeManaged; +#else + return false; +#endif +} + +} // namespace metal +} // namespace hal +} // namespace iree
diff --git a/iree/hal/metal/metal_command_buffer.h b/iree/hal/metal/metal_command_buffer.h index c3d532e..c58e5be 100644 --- a/iree/hal/metal/metal_command_buffer.h +++ b/iree/hal/metal/metal_command_buffer.h
@@ -18,6 +18,7 @@ #import <Metal/Metal.h> #include "iree/hal/command_buffer.h" +#include "iree/hal/metal/metal_buffer.h" namespace iree { namespace hal { @@ -91,8 +92,23 @@ CommandCategoryBitfield command_categories, id<MTLCommandBuffer> command_buffer); + StatusOr<MetalBuffer*> CastBuffer(Buffer* buffer) const; + + // Gets or begins an active MTLBlitCommandEncoder. This also ends all previous + // encoded compute commands if any. + id<MTLBlitCommandEncoder> GetOrBeginBlitEncoder(); + void EndBlitEncoder(); + + // Gets or begins a new MTLComputeCommandEncoder. This also ends all previous + // encoded blit commands if any. + id<MTLComputeCommandEncoder> GetOrBeginComputeEncoder(); + void EndComputeEncoder(); + bool is_recording_ = false; id<MTLCommandBuffer> metal_handle_; + + id<MTLComputeCommandEncoder> current_compute_encoder_ = nil; + id<MTLBlitCommandEncoder> current_blit_encoder_ = nil; }; } // namespace metal
diff --git a/iree/hal/metal/metal_command_buffer.mm b/iree/hal/metal/metal_command_buffer.mm index 17dec6c..8555793 100644 --- a/iree/hal/metal/metal_command_buffer.mm +++ b/iree/hal/metal/metal_command_buffer.mm
@@ -14,6 +14,7 @@ #include "iree/hal/metal/metal_command_buffer.h" +#include "iree/base/logging.h" #include "iree/base/status.h" #include "iree/base/tracing.h" @@ -38,6 +39,58 @@ [metal_handle_ release]; } +StatusOr<MetalBuffer*> MetalCommandBuffer::CastBuffer(Buffer* buffer) const { + // TODO(benvanik): assert that the buffer is from the right allocator and + // that it is compatible with our target queue family. + return static_cast<MetalBuffer*>(buffer->allocated_buffer()); +} + +id<MTLBlitCommandEncoder> MetalCommandBuffer::GetOrBeginBlitEncoder() { + IREE_TRACE_SCOPE0("MetalCommandBuffer::GetOrBeginBlitEncoder"); + + if (current_compute_encoder_) EndComputeEncoder(); + + @autoreleasepool { + if (!current_blit_encoder_) { + current_blit_encoder_ = [[metal_handle_ blitCommandEncoder] retain]; + } + } + + return current_blit_encoder_; +} + +void MetalCommandBuffer::EndBlitEncoder() { + IREE_TRACE_SCOPE0("MetalCommandBuffer::EndBlitEncoder"); + if (current_blit_encoder_) { + [current_blit_encoder_ endEncoding]; + [current_blit_encoder_ release]; + current_blit_encoder_ = nil; + } +} + +id<MTLComputeCommandEncoder> MetalCommandBuffer::GetOrBeginComputeEncoder() { + IREE_TRACE_SCOPE0("MetalCommandBuffer::GetOrBeginComputeEncoder"); + + if (current_blit_encoder_) EndBlitEncoder(); + + @autoreleasepool { + if (!current_compute_encoder_) { + current_compute_encoder_ = [[metal_handle_ computeCommandEncoder] retain]; + } + } + + return current_compute_encoder_; +} + +void MetalCommandBuffer::EndComputeEncoder() { + IREE_TRACE_SCOPE0("MetalCommandBuffer::EndComputeEncoder"); + if (current_compute_encoder_) { + [current_compute_encoder_ endEncoding]; + [current_compute_encoder_ release]; + current_compute_encoder_ = nil; + } +} + Status MetalCommandBuffer::Begin() { IREE_TRACE_SCOPE0("MetalCommandBuffer::Begin"); is_recording_ = true; @@ -46,6 +99,8 @@ Status MetalCommandBuffer::End() { IREE_TRACE_SCOPE0("MetalCommandBuffer::End"); + EndBlitEncoder(); + EndComputeEncoder(); is_recording_ = false; return OkStatus(); } @@ -81,12 +136,39 @@ device_size_t length, const void* pattern, size_t pattern_length) { IREE_TRACE_SCOPE0("MetalCommandBuffer::FillBuffer"); - return UnimplementedErrorBuilder(IREE_LOC) << "MetalCommandBuffer::FillBuffer"; + IREE_ASSIGN_OR_RETURN(auto* target_device_buffer, CastBuffer(target_buffer)); + + target_offset += target_buffer->byte_offset(); + + // Per the spec for fillBuffer:range:value: "The alignment and length of the range must both be a + // multiple of 4 bytes in macOS, and 1 byte in iOS and tvOS." Although iOS/tvOS is more relaxed on + // this front, we still require 4-byte alignment for uniformity across IREE. + if (target_offset % 4 != 0) { + return UnimplementedErrorBuilder(IREE_LOC) + << "MetalCommandBuffer::FillBuffer with offset that is not a multiple of 4 bytes"; + } + + // Note that fillBuffer:range:value: only accepts a single byte as the pattern but FillBuffer + // can accept 1/2/4 bytes. If the pattern itself contains repeated bytes, we can call into + // fillBuffer:range:value:. Otherwise we may need to find another way. Just implement the case + // where we have a single byte to fill for now. + if (pattern_length != 1) { + return UnimplementedErrorBuilder(IREE_LOC) + << "MetalCommandBuffer::FillBuffer with non-1-byte pattern"; + } + uint8_t byte_pattern = *reinterpret_cast<const uint8_t*>(pattern); + + [GetOrBeginBlitEncoder() fillBuffer:target_device_buffer->handle() + range:NSMakeRange(target_offset, length) + value:byte_pattern]; + + return OkStatus(); } Status MetalCommandBuffer::DiscardBuffer(Buffer* buffer) { IREE_TRACE_SCOPE0("MetalCommandBuffer::DiscardBuffer"); - return UnimplementedErrorBuilder(IREE_LOC) << "MetalCommandBuffer::DiscardBuffer"; + // This is a hint. Nothing to do for Metal. + return OkStatus(); } Status MetalCommandBuffer::UpdateBuffer(const void* source_buffer, device_size_t source_offset, @@ -100,7 +182,28 @@ Buffer* target_buffer, device_size_t target_offset, device_size_t length) { IREE_TRACE_SCOPE0("MetalCommandBuffer::CopyBuffer"); - return UnimplementedErrorBuilder(IREE_LOC) << "MetalCommandBuffer::CopyBuffer"; + + IREE_ASSIGN_OR_RETURN(auto* source_device_buffer, CastBuffer(source_buffer)); + IREE_ASSIGN_OR_RETURN(auto* target_device_buffer, CastBuffer(target_buffer)); + + source_offset += source_buffer->byte_offset(); + target_offset += target_buffer->byte_offset(); + + // Per the spec for copyFromBuffer:sourceOffset:toBuffer:destinationOffset:size, the source/target + // offset must be a multiple of 4 bytes in macOS, and 1 byte in iOS and tvOS. Although iOS/tvOS + // is more relaxed on this front, we still require 4-byte alignment for uniformity across IREE. + if (source_offset % 4 != 0 || target_offset % 4 != 0) { + return UnimplementedErrorBuilder(IREE_LOC) + << "MetalCommandBuffer::CopyBuffer with offset that is not a multiple of 4 bytes"; + } + + [GetOrBeginBlitEncoder() copyFromBuffer:source_device_buffer->handle() + sourceOffset:source_offset + toBuffer:target_device_buffer->handle() + destinationOffset:target_offset + size:length]; + + return OkStatus(); } Status MetalCommandBuffer::PushConstants(ExecutableLayout* executable_layout, size_t offset,
diff --git a/iree/hal/metal/metal_device.h b/iree/hal/metal/metal_device.h index 7802664..729cbaf 100644 --- a/iree/hal/metal/metal_device.h +++ b/iree/hal/metal/metal_device.h
@@ -17,6 +17,8 @@ #import <Metal/Metal.h> +#include <memory> + #include "absl/types/span.h" #include "iree/base/memory.h" #include "iree/hal/allocator.h" @@ -40,7 +42,7 @@ std::string DebugString() const override; - Allocator* allocator() const override { return nullptr; } + Allocator* allocator() const override { return allocator_.get(); } absl::Span<CommandQueue*> dispatch_queues() const override { return absl::MakeSpan(&common_queue_, 1); @@ -84,6 +86,8 @@ ref_ptr<Driver> driver_; id<MTLDevice> metal_handle_; + std::unique_ptr<Allocator> allocator_; + // Metal does not have clear graphics/dispatch/transfer queue distinction like // Vulkan; one just use the same newCommandQueue() API call on MTLDevice to // get command queues. Command encoders differ for different categories of
diff --git a/iree/hal/metal/metal_device.mm b/iree/hal/metal/metal_device.mm index f293641..7f97422 100644 --- a/iree/hal/metal/metal_device.mm +++ b/iree/hal/metal/metal_device.mm
@@ -19,10 +19,12 @@ #include "iree/base/status.h" #include "iree/base/time.h" #include "iree/base/tracing.h" +#include "iree/hal/allocator.h" #include "iree/hal/command_buffer_validation.h" #include "iree/hal/metal/dispatch_time_util.h" #include "iree/hal/metal/metal_command_buffer.h" #include "iree/hal/metal/metal_command_queue.h" +#include "iree/hal/metal/metal_direct_allocator.h" #include "iree/hal/metal/metal_shared_event.h" namespace iree { @@ -44,6 +46,9 @@ // Grab one queue for dispatch and transfer. std::string name = absl::StrCat(device_info.name(), ":queue"); id<MTLCommandQueue> metal_queue = [metal_handle_ newCommandQueue]; // retained + + allocator_ = MetalDirectAllocator::Create(metal_handle_, metal_queue); + command_queue_ = absl::make_unique<MetalCommandQueue>( name, CommandCategory::kDispatch | CommandCategory::kTransfer, metal_queue); common_queue_ = command_queue_.get();
diff --git a/iree/hal/metal/metal_direct_allocator.h b/iree/hal/metal/metal_direct_allocator.h new file mode 100644 index 0000000..bf8dde8 --- /dev/null +++ b/iree/hal/metal/metal_direct_allocator.h
@@ -0,0 +1,82 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 IREE_HAL_METAL_METAL_DIRECT_ALLOCATOR_H_ +#define IREE_HAL_METAL_METAL_DIRECT_ALLOCATOR_H_ + +#import <Metal/Metal.h> + +#include <memory> + +#include "iree/base/status.h" +#include "iree/hal/allocator.h" + +namespace iree { +namespace hal { +namespace metal { + +class MetalBuffer; + +// An allocator implementation for Metal that directly wraps a MTLDevice and +// requests all allocations on the device. This is not of great performance, +// but good for start. +class MetalDirectAllocator final : public Allocator { + public: + static std::unique_ptr<MetalDirectAllocator> Create( + id<MTLDevice> device, id<MTLCommandQueue> transfer_queue); + + ~MetalDirectAllocator() override; + + bool CanUseBufferLike(Allocator* source_allocator, + MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + BufferUsageBitfield intended_usage) const override; + + bool CanAllocate(MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + size_t allocation_size) const override; + + Status MakeCompatible(MemoryTypeBitfield* memory_type, + BufferUsageBitfield* buffer_usage) const override; + + StatusOr<ref_ptr<Buffer>> Allocate(MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + size_t allocation_size) override; + + StatusOr<ref_ptr<Buffer>> AllocateConstant( + BufferUsageBitfield buffer_usage, ref_ptr<Buffer> source_buffer) override; + + StatusOr<ref_ptr<Buffer>> WrapMutable(MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, + BufferUsageBitfield buffer_usage, + void* data, + size_t data_length) override; + + private: + explicit MetalDirectAllocator(id<MTLDevice> device, + id<MTLCommandQueue> transfer_queue); + + StatusOr<ref_ptr<MetalBuffer>> AllocateInternal( + MemoryTypeBitfield memory_type, BufferUsageBitfield buffer_usage, + MemoryAccessBitfield allowed_access, size_t allocation_size); + + id<MTLDevice> metal_device_; + id<MTLCommandQueue> metal_transfer_queue_; +}; + +} // namespace metal +} // namespace hal +} // namespace iree + +#endif // IREE_HAL_METAL_METAL_DIRECT_ALLOCATOR_H_
diff --git a/iree/hal/metal/metal_direct_allocator.mm b/iree/hal/metal/metal_direct_allocator.mm new file mode 100644 index 0000000..633bd41 --- /dev/null +++ b/iree/hal/metal/metal_direct_allocator.mm
@@ -0,0 +1,157 @@ +// Copyright 2020 Google LLC +// +// 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 +// +// https://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 "iree/hal/metal/metal_direct_allocator.h" + +#include "absl/memory/memory.h" +#include "iree/base/status.h" +#include "iree/base/tracing.h" +#include "iree/hal/metal/metal_buffer.h" + +namespace iree { +namespace hal { +namespace metal { + +namespace { + +// Returns the proper Metal resource storage mode given the specific MemoryType. +MTLResourceOptions SelectMTLResourceStorageMode(MemoryType memory_type) { + // There are four MTLStorageMode: + // * Managed: The CPU and GPU may maintain separate copies of the resource, and any changes + // must be explicitly synchronized. + // * Shared: The resource is stored in system memory and is accessible to both the CPU and + // the GPU. + // * Private: The resource can be accessed only by the GPU. + // * Memoryless: The resource’s contents can be accessed only by the GPU and only exist + // temporarily during a render pass. + // macOS has all of the above; MTLStorageModeManaged is not available on iOS. + // + // The IREE HAL is modeled after Vulkan so it's quite explicit. For buffers visible to both + // the host and the device, we would like to opt in with the explicit version + // (MTLStorageManaged) when possible because it should be more performant: "In macOS, + // there’s no difference in GPU performance between managed and private buffers." But for + // iOS, MTLStorageShared should be good given we have a unified memory model there. + + if (AllBitsSet(memory_type, MemoryType::kDeviceLocal)) { + if (AllBitsSet(memory_type, MemoryType::kHostVisible)) { + // Device-local, host-visible. +#ifdef IREE_PLATFORM_MACOS + return MTLResourceStorageModeManaged; +#else + return MTLResourceStorageModeShared; +#endif + } else { + // Device-local only. + return MTLResourceStorageModePrivate; + } + } else { + if (AllBitsSet(memory_type, MemoryType::kDeviceVisible)) { + // Host-local, device-visible. + return MTLResourceStorageModeShared; + } else { + // Host-local only. + // TODO(antiagainst): we probably want to just use HostBuffer here. + return MTLResourceStorageModeShared; + } + } +} + +} // namespace + +// static +std::unique_ptr<MetalDirectAllocator> MetalDirectAllocator::Create( + id<MTLDevice> device, id<MTLCommandQueue> transfer_queue) { + IREE_TRACE_SCOPE0("MetalDirectAllocator::Create"); + return absl::WrapUnique(new MetalDirectAllocator(device, transfer_queue)); +} + +MetalDirectAllocator::MetalDirectAllocator(id<MTLDevice> device, id<MTLCommandQueue> transfer_queue) + : metal_device_([device retain]), metal_transfer_queue_([transfer_queue retain]) {} + +MetalDirectAllocator::~MetalDirectAllocator() { + IREE_TRACE_SCOPE0("MetalDirectAllocator::dtor"); + [metal_transfer_queue_ release]; + [metal_device_ release]; +} + +bool MetalDirectAllocator::CanUseBufferLike(Allocator* source_allocator, + MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + BufferUsageBitfield intended_usage) const { + // TODO(benvanik): ensure there is a memory type that can satisfy the request. + return source_allocator == this; +} + +bool MetalDirectAllocator::CanAllocate(MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + size_t allocation_size) const { + // TODO(benvanik): ensure there is a memory type that can satisfy the request. + return true; +} + +Status MetalDirectAllocator::MakeCompatible(MemoryTypeBitfield* memory_type, + BufferUsageBitfield* buffer_usage) const { + // TODO(benvanik): mutate to match supported memory types. + return OkStatus(); +} + +StatusOr<ref_ptr<MetalBuffer>> MetalDirectAllocator::AllocateInternal( + MemoryTypeBitfield memory_type, BufferUsageBitfield buffer_usage, + MemoryAccessBitfield allowed_access, size_t allocation_size) { + IREE_TRACE_SCOPE0("MetalDirectAllocator::AllocateInternal"); + + MTLResourceOptions resource_options = SelectMTLResourceStorageMode(memory_type); + + // IREE is more explicit than Metal: it tracks various state by itself. There is no need + // to incur Metal runtime overhead for hazard tracking. + resource_options |= MTLResourceHazardTrackingModeUntracked; + + id<MTLBuffer> metal_buffer = [metal_device_ newBufferWithLength:allocation_size + options:resource_options]; // retained + + return MetalBuffer::CreateUnretained( + this, memory_type, allowed_access, buffer_usage, allocation_size, /*byte_offset=*/0, + /*byte_length=*/allocation_size, metal_buffer, metal_transfer_queue_); +} + +StatusOr<ref_ptr<Buffer>> MetalDirectAllocator::Allocate(MemoryTypeBitfield memory_type, + BufferUsageBitfield buffer_usage, + size_t allocation_size) { + IREE_TRACE_SCOPE0("MetalDirectAllocator::Allocate"); + return AllocateInternal(memory_type, buffer_usage, MemoryAccess::kAll, allocation_size); +} + +StatusOr<ref_ptr<Buffer>> MetalDirectAllocator::AllocateConstant(BufferUsageBitfield buffer_usage, + ref_ptr<Buffer> source_buffer) { + IREE_TRACE_SCOPE0("MetalDirectAllocator::AllocateConstant"); + // TODO(benvanik): import memory to avoid the copy. + IREE_ASSIGN_OR_RETURN( + auto buffer, AllocateInternal(MemoryType::kDeviceLocal | MemoryType::kHostVisible, + buffer_usage, MemoryAccess::kRead | MemoryAccess::kDiscardWrite, + source_buffer->byte_length())); + IREE_RETURN_IF_ERROR(buffer->CopyData(0, source_buffer.get(), 0, kWholeBuffer)); + return buffer; +} + +StatusOr<ref_ptr<Buffer>> MetalDirectAllocator::WrapMutable(MemoryTypeBitfield memory_type, + MemoryAccessBitfield allowed_access, + BufferUsageBitfield buffer_usage, + void* data, size_t data_length) { + IREE_TRACE_SCOPE0("MetalDirectAllocator::WrapMutable"); + return UnimplementedErrorBuilder(IREE_LOC) << "MetalDirectAllocator::WrapMutable"; +} + +} // namespace metal +} // namespace hal +} // namespace iree
diff --git a/iree/hal/vulkan/vma_buffer.cc b/iree/hal/vulkan/vma_buffer.cc index 51880d2..6496df9 100644 --- a/iree/hal/vulkan/vma_buffer.cc +++ b/iree/hal/vulkan/vma_buffer.cc
@@ -55,21 +55,19 @@ case 1: { uint8_t* data = static_cast<uint8_t*>(data_ptr); uint8_t value_bits = *static_cast<const uint8_t*>(pattern); - std::fill_n(data + byte_offset, byte_length, value_bits); + std::fill_n(data, byte_length, value_bits); break; } case 2: { uint16_t* data = static_cast<uint16_t*>(data_ptr); uint16_t value_bits = *static_cast<const uint16_t*>(pattern); - std::fill_n(data + byte_offset / sizeof(uint16_t), - byte_length / sizeof(uint16_t), value_bits); + std::fill_n(data, byte_length / sizeof(uint16_t), value_bits); break; } case 4: { uint32_t* data = static_cast<uint32_t*>(data_ptr); uint32_t value_bits = *static_cast<const uint32_t*>(pattern); - std::fill_n(data + byte_offset / sizeof(uint32_t), - byte_length / sizeof(uint32_t), value_bits); + std::fill_n(data, byte_length / sizeof(uint32_t), value_bits); break; } default: @@ -111,8 +109,8 @@ MapMemory<uint8_t>(MemoryAccess::kDiscardWrite, target_offset, data_length)); CHECK_EQ(data_length, target_mapping.size()); - std::memcpy(target_mapping.mutable_data() + target_offset, - source_mapping.data(), data_length); + std::memcpy(target_mapping.mutable_data(), source_mapping.data(), + data_length); return OkStatus(); }
diff --git a/iree/test/e2e/xla_ops/BUILD b/iree/test/e2e/xla_ops/BUILD index 66a9fcf..fbcd1d2 100644 --- a/iree/test/e2e/xla_ops/BUILD +++ b/iree/test/e2e/xla_ops/BUILD
@@ -40,23 +40,20 @@ "abs.mlir", "add.mlir", "batch_norm_inference.mlir", - "broadcast_add.mlir", "broadcast.mlir", + "broadcast_add.mlir", "broadcast_in_dim.mlir", "clamp.mlir", "compare.mlir", - "convolution.mlir", - "convert.mlir", "concatenate.mlir", "constant.mlir", + "convert.mlir", + "convolution.mlir", "cosine.mlir", "divide.mlir", "dot.mlir", "exponential.mlir", - - # TODO(#1696): Enable after standard dialect can support floor - # operation. Lowering from XLA -> linalg should be easy fix. - # "floor.mlir", + "floor.mlir", "gather.mlir", "gather_concat.mlir", "iota.mlir", @@ -107,6 +104,7 @@ "divide.mlir", "dot.mlir", "exponential.mlir", + "floor.mlir", "gather.mlir", "gather_concat.mlir", "iota.mlir",
diff --git a/iree/test/e2e/xla_ops/CMakeLists.txt b/iree/test/e2e/xla_ops/CMakeLists.txt index 0303432..c44290a 100644 --- a/iree/test/e2e/xla_ops/CMakeLists.txt +++ b/iree/test/e2e/xla_ops/CMakeLists.txt
@@ -46,6 +46,7 @@ "divide.mlir" "dot.mlir" "exponential.mlir" + "floor.mlir" "gather.mlir" "gather_concat.mlir" "iota.mlir" @@ -96,6 +97,7 @@ "divide.mlir" "dot.mlir" "exponential.mlir" + "floor.mlir" "gather.mlir" "gather_concat.mlir" "iota.mlir"
diff --git a/iree/vm/native_module_test.h b/iree/vm/native_module_test.h index ce08749..35de2d5 100644 --- a/iree/vm/native_module_test.h +++ b/iree/vm/native_module_test.h
@@ -187,6 +187,7 @@ IREE_RETURN_IF_ERROR( iree_allocator_malloc(allocator, sizeof(*state), (void**)&state)); memset(state, 0, sizeof(*state)); + state->allocator = allocator; *out_module_state = (iree_vm_module_state_t*)state; return iree_ok_status(); } @@ -281,6 +282,7 @@ IREE_RETURN_IF_ERROR( iree_allocator_malloc(allocator, sizeof(*module), (void**)&module)); memset(module, 0, sizeof(*module)); + module->allocator = allocator; // Resolve types used by the module once so that we can share it across all // instances of the module.