Make better use of toolchains in the build.

This allows building for different platforms without having to retarget
the build. The configuration is also better checked by the assertions in
the toolchain templates.

A future change will redirect the root build rule to allow BSPs to
fully control their own build and configuration.

Change-Id: Iaae725d3bd000bc0ce7b9ef0f8f083350a73bd16
diff --git a/BUILD.gn b/BUILD.gn
index 8258888..ff5d514 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -12,47 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//build/image/image.gni")
-
-group("tests") {
+group("all") {
   testonly = true
 
   deps = [
-    ":unit_tests",
-    ":vm_tests",
+    "//src:hafnium(//build/toolchain:aem_v8a_fvp_clang)",
+    "//src:hafnium(//build/toolchain:hikey_clang)",
+    "//src:hafnium(//build/toolchain:qemu_aarch64_clang)",
+    "//src:unit_tests(//build/toolchain:host_fake_clang)",
+    "//test/vm:vm_tests(//build/toolchain:qemu_aarch64_clang)",
   ]
-}
 
-group("unit_tests") {
-  testonly = true
-
-  deps = [
-    "//src:unit_tests($fake_toolchain)",
-  ]
-}
-
-group("vm_tests") {
-  testonly = true
-
-  deps = [
-    ":hypervisor",
-    "//test/vm/:primary_only_test($arch_toolchain)",
-    "//test/vm/:primary_with_secondaries_test($arch_toolchain)",
-  ]
-}
-
-group("hypervisor") {
-  deps = [
-    ":hafnium($arch_toolchain)",
-  ]
-}
-
-# Only build the image for the arch
-if (current_toolchain == arch_toolchain) {
-  hypervisor("hafnium") {
-    deps = [
-      "//src",
-      "//src/arch/${arch}",
-    ]
-  }
+  # TODO: add a gcc-4.9 or above prebuilt to check the gcc build too?
 }
diff --git a/Makefile b/Makefile
index 09586c6..b5c4695 100644
--- a/Makefile
+++ b/Makefile
@@ -5,37 +5,21 @@
 NINJA ?= $(PREBUILTS)/ninja/ninja
 export PATH := $(PREBUILTS)/clang/bin:$(PATH)
 
-# Configure the build arguments.
-ARCH ?= aarch64
-PLATFORM ?= qemu
-GCC ?= false
-
 # Place builds for different architectures and platforms in different
 # directories.
 OUT ?= out
-OUT_DIR = out/$(ARCH)/$(PLATFORM)
+OUT_DIR = out
 
 .PHONY: all
 all: $(OUT_DIR)/build.ninja
 	@$(NINJA) -C $(OUT_DIR)
 
-$(OUT_DIR)/build.ninja: $(OUT_DIR)/args.gn
+$(OUT_DIR)/build.ninja:
 	@$(GN) --export-compile-commands gen $(OUT_DIR)
 
-# Configure the build by loading the configuration arguments for the
-# architecture and platform.
-$(OUT_DIR)/args.gn: build/arch/$(ARCH)/$(PLATFORM).args
-	@echo Copying config for $(ARCH) on $(PLATFORM)
-	@mkdir -p $(OUT_DIR)
-	@echo "arch = \"$(ARCH)\"" >> $@
-	@echo "use_gcc = $(GCC)" >> $@
-	@echo >> $@
-	@cat $< >> $@
-
 .PHONY: clean
 clean:
 	@$(NINJA) -C $(OUT_DIR) -t clean
-	rm -f $(OUT_DIR)/args.gn
 
 .PHONY: clobber
 clobber:
diff --git a/build/BUILD.gn b/build/BUILD.gn
index 9a6eaf2..46c73c7 100644
--- a/build/BUILD.gn
+++ b/build/BUILD.gn
@@ -12,9 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//build/arch/common.gni")
-import("//build/arch/aarch64.gni")  # TODO: remove when no dependency on arch_aarch64_pl011_base_address
+import("//build/toolchain/platform.gni")
 
+# Default language and error reporting configuration.
 config("compiler_defaults") {
   cflags = [
     "-g",
@@ -29,65 +29,24 @@
   cflags_c = [ "-std=c11" ]
 
   cflags_cc = [ "-std=c++14" ]
-
-  if (current_toolchain == fake_toolchain ||
-      current_toolchain == arch_toolchain) {
-    include_dirs = [
-      "//inc",
-      "//inc/vmapi",
-      "//src/arch/${arch}/inc",
-    ]
-
-    defines = [
-      # TODO: move these build args for the platforms
-      "MAX_CPUS=8",
-      "MAX_VMS=16",
-      "STACK_SIZE=4096",
-    ]
-
-    if (is_debug) {
-      defines += [ "DEBUG=1" ]
-    } else {
-      defines += [ "DEBUG=0" ]
-    }
-
-    # Configuration specific for the bare metal images.
-    if (current_toolchain == arch_toolchain) {
-      include_dirs += [ "${root_gen_dir}/inc" ]
-
-      cflags += [
-        "-mcpu=${arch_cpu}",
-        "-mstrict-align",
-        "-fno-stack-protector",
-        "-fno-builtin",
-        "-ffreestanding",
-        "-fpic",
-      ]
-
-      # TODO: this should be arch specific but it is currenly used by the
-      # platform generic code to map the memory
-      if (arch_aarch64_use_pl011) {
-        defines += [ "PL011_BASE=${arch_aarch64_pl011_base_address}" ]
-      }
-    }
-  }
 }
 
-config("executable_ldconfig") {
-  if (current_toolchain == arch_toolchain) {
-    ldflags = [ "--gc-sections" ]
-  }
-}
-
-config("sections") {
-  cflags = [
-    "-ffunction-sections",
-    "-fdata-sections",
+# Platform configuration.
+config("platform") {
+  include_dirs = [
+    "//inc",
+    "//inc/vmapi",
+    "//src/arch/${arch}/inc",
   ]
-}
 
-config("lto") {
-  if (current_toolchain == arch_toolchain) {
-    cflags = [ "-flto" ]
+  defines = [
+    "MAX_CPUS=${platform_max_cpus}",
+    "MAX_VMS=${platform_max_vms}",
+  ]
+
+  if (is_debug) {
+    defines += [ "DEBUG=1" ]
+  } else {
+    defines += [ "DEBUG=0" ]
   }
 }
diff --git a/build/BUILDCONFIG.gn b/build/BUILDCONFIG.gn
index 62cf43d..aca2f24 100644
--- a/build/BUILDCONFIG.gn
+++ b/build/BUILDCONFIG.gn
@@ -12,42 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# This build configuration extends GN's cross-compilation concepts of "target",
-# "host" and "current" with "arch" which is used to refer to the embedded
-# architecture that the bare metal images are being built for.
-#
-# The concepts of "target", "host" and "current" are used when bulding tests
-# and utilities for the non-embedded device. Note that there hasn't been any
-# thought given to support building for anything other than the host so it
-# probably won't work.
-#
-# In summary:
-#  - host{_os,_cpu,_toolchain} is for the system running the build
-#  - target{_os,_cpu,_toolchain} is for the system that will run utilities and tests
-#  - arch{,_toolchain} is for the embedded system that will run the bare metal images
-
 # Configuration of the build toolchain.
 declare_args() {
   # Enable extra debugging.
   is_debug = true
 
-  # Set to true to build with gcc (default is clang).
-  use_gcc = false
-
   # The architecture to build the bare metal images for.
-  arch = ""
-
-  # Set by arch toolchain. Prefix for binutils tools.
-  arch_tool_prefix = ""
+  arch = "host"
 }
 
-# Check that we support the attempted build
-assert(host_os == "linux", "Only linux builds are currently supported")
+# Check that we support the attempted build.
+assert(host_os == "linux", "Only linux builds are currently supported.")
 
-# Check that the requested architecture is supported
-assert(arch == "aarch64" || arch == "fake", "Unsupported arch: $arch")
-
-# Setup the standard variables
+# Setup the standard variables.
 if (target_os == "") {
   target_os = host_os
 }
@@ -61,22 +38,20 @@
   current_cpu = target_cpu
 }
 
-assert(target_os == host_os, "Cross compiles not yet supported")
-assert(target_cpu == host_cpu, "Cross compiles not yet supported")
+assert(target_os == host_os, "Cross compiles not yet supported.")
+assert(target_cpu == host_cpu, "Cross compiles not yet supported.")
 
-# All binary targets will get this list of configs by default
-_shared_binary_target_configs = [
-  "//build:compiler_defaults",
-  "//build:sections",
-  "//build:lto",
-]
+# All binary targets will get this list of configs by default.
+_shared_binary_target_configs = [ "//build:compiler_defaults" ]
+
+# If it's not building a host utility, it's building against the platform so apply the configuration.
+if (arch != "host") {
+  _shared_binary_target_configs += [ "//build:platform" ]
+}
 
 # Apply that default list to the binary target types.
 set_defaults("executable") {
   configs = _shared_binary_target_configs
-
-  # Executables get this additional configuration
-  configs += [ "//build:executable_ldconfig" ]
 }
 set_defaults("static_library") {
   configs = _shared_binary_target_configs
@@ -88,18 +63,5 @@
   configs = _shared_binary_target_configs
 }
 
-# Select host, target and arch toolchains
-if (use_gcc) {
-  host_toolchain = "//build/toolchain/host:gcc"
-  target_toolchain = "//build/toolchain/host:gcc"
-  fake_toolchain = "//build/toolchain/host:gcc_fake_arch"
-  arch_toolchain = "//build/toolchain/arch:gcc_${arch}"
-} else {
-  host_toolchain = "//build/toolchain/host:clang"
-  target_toolchain = "//build/toolchain/host:clang"
-  fake_toolchain = "//build/toolchain/host:clang_fake_arch"
-  arch_toolchain = "//build/toolchain/arch:clang_${arch}"
-}
-
-# The default toolchain is the target toolchain for building utilities and tests
-set_default_toolchain(target_toolchain)
+# The default toolchain is the target toolchain for building utilities and tests.
+set_default_toolchain("//build/toolchain:host_clang")
diff --git a/build/arch/aarch64/fvp.args b/build/arch/aarch64/fvp.args
deleted file mode 100644
index ecb4af1..0000000
--- a/build/arch/aarch64/fvp.args
+++ /dev/null
@@ -1,5 +0,0 @@
-arch_cpu = "cortex-a57+nofp"
-arch_hypervisor_origin_address = "0x88000000"
-
-arch_aarch64_use_pl011 = true
-arch_aarch64_pl011_base_address = "0x1c090000"
diff --git a/build/arch/aarch64/hikey.args b/build/arch/aarch64/hikey.args
deleted file mode 100644
index d08989d..0000000
--- a/build/arch/aarch64/hikey.args
+++ /dev/null
@@ -1,6 +0,0 @@
-arch_cpu = "cortex-a57+nofp"
-arch_hypervisor_origin_address = "0x01000000"
-
-# Use UART3.
-arch_aarch64_use_pl011 = true
-arch_aarch64_pl011_base_address = "0xf7113000"
diff --git a/build/arch/aarch64/qemu.args b/build/arch/aarch64/qemu.args
deleted file mode 100644
index a822859..0000000
--- a/build/arch/aarch64/qemu.args
+++ /dev/null
@@ -1,5 +0,0 @@
-arch_cpu = "cortex-a57+nofp"
-arch_hypervisor_origin_address = "0x40001000"
-
-arch_aarch64_use_pl011 = true
-arch_aarch64_pl011_base_address = "0x09000000"
diff --git a/build/arch/common.gni b/build/arch/common.gni
deleted file mode 100644
index ca2e285..0000000
--- a/build/arch/common.gni
+++ /dev/null
@@ -1,26 +0,0 @@
-# Copyright 2018 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.
-
-declare_args() {
-  # The specific CPU that runs the architecture.
-  arch_cpu = ""
-
-  # The origin address of the hypervisor image
-  arch_hypervisor_origin_address = ""
-}
-
-assert(arch_cpu != "", "Must provide the CPU to build for as \"arch_cpu\".")
-assert(
-    arch_hypervisor_origin_address != "",
-    "Must provide the origin address of the hypervisor image \"arch_hypervisor_origin_address\".")
diff --git a/build/image/extract_offsets.py b/build/image/extract_offsets.py
deleted file mode 100644
index e0a2b4a..0000000
--- a/build/image/extract_offsets.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2018 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.
-
-"""Extract embedded offsets from an object file.
-
-We let the compiler calculate the offsets it is going to use and have those
-emitted into and object file. This is the next pass which extracts those offsets
-and stores them in a header file for the assembly to include and use.
-"""
-
-import argparse
-import re
-import subprocess
-import sys
-
-
-def Main():
-    parser = argparse.ArgumentParser()
-    parser.add_argument("--tool_prefix", required=True)
-    parser.add_argument("--input", required=True)
-    parser.add_argument("--output", required=True)
-    args = parser.parse_args()
-    raw = subprocess.check_output([
-        "{}objdump".format(args.tool_prefix), "--disassemble-all", "--section",
-        ".rodata", args.input
-    ])
-    lines = raw.split('\n')
-    with open(args.output, 'w') as header:
-        header.write('#pragma once\n\n')
-        for n in range(len(lines)):
-            # Find a defined offset
-            match = re.match('.+DEFINE_OFFSET__([^>]+)', lines[n])
-            if not match:
-                continue
-            name = match.group(1)
-            # The next line tells the offset
-            if "..." in lines[n + 1]:
-                offset = 0
-            else:
-                offset = re.match('.+\.[\S]+\s+(.+)$', lines[n + 1]).group(1)
-            # Write the offset to the header
-            header.write("#define {} {}\n".format(name, offset))
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(Main())
diff --git a/build/image/generate_offsets.gni b/build/image/generate_offsets.gni
deleted file mode 100644
index cce64bd..0000000
--- a/build/image/generate_offsets.gni
+++ /dev/null
@@ -1,92 +0,0 @@
-# Copyright 2018 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.
-
-# Calculates offsets of fields in C structures for use in assembly.
-template("generate_offsets") {
-  # There are 3 targets involved:
-  #  1. have the compiler calculate the offsets
-  #  2. extract those offsets to a header file
-  #  3. include the offsets in the build for validation
-  obj_target = "${target_name}__obj"
-  header_target = "${target_name}__header"
-  validate_target = target_name
-
-  # Have the compiler calculate the offsets and store that information in the
-  # object file to extract later.
-  source_set(obj_target) {
-    forward_variables_from(invoker,
-                           [
-                             "cflags",
-                             "cflags_c",
-                             "defines",
-                             "deps",
-                             "public_deps",
-                             "sources",
-                             "testonly",
-                           ])
-
-    # Disable LTO so we get an object file.
-    configs -= [
-      "//build:sections",
-      "//build:lto",
-    ]
-    defines = [ "GEN_OFFSETS" ]
-    visibility = [ ":${header_target}" ]
-  }
-
-  # Extract the offset information we've had the compiler emit into the object
-  # file.
-  action_foreach("${header_target}") {
-    forward_variables_from(invoker,
-                           [
-                             "sources",
-                             "testonly",
-                           ])
-    script = "//build/image/extract_offsets.py"
-    deps = [
-      ":${obj_target}",
-    ]
-    args = [
-      "--tool_prefix",
-      arch_tool_prefix,
-      "--input",
-      rebase_path("${target_out_dir}/${obj_target}.{{source_name_part}}.o"),
-      "--output",
-      rebase_path("${root_gen_dir}/inc/{{source_name_part}}.h"),
-    ]
-    outputs = [
-      "${root_gen_dir}/inc/{{source_name_part}}.h",
-    ]
-    visibility = [ ":${validate_target}" ]
-  }
-
-  # Include the offset source file in the build so the extracted offsets can be
-  # validated.
-  source_set(validate_target) {
-    forward_variables_from(invoker,
-                           [
-                             "cflags",
-                             "cflags_c",
-                             "defines",
-                             "deps",
-                             "public_deps",
-                             "sources",
-                             "testonly",
-                           ])
-
-    deps = [
-      ":${header_target}",
-    ]
-  }
-}
diff --git a/build/image/image.gni b/build/image/image.gni
index b1b1895..2f9da5b 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -12,14 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//build/arch/common.gni")
+import("//build/toolchain/embedded.gni")
 
 # Build image, link to an ELF file then convert to plain binary.
 template("image_binary") {
   assert(defined(invoker.image_name),
          "image_binary() must specify an \"image_name\" value")
-  assert(defined(invoker.origin_address),
-         "image_binary() must specify an \"origin_address\" value")
 
   output_root = ""
   if (defined(invoker.output_path)) {
@@ -44,10 +42,8 @@
       rebase_path("//build/image/image.ld"),
     ]
     ldflags = [
-      "-pie",
       "-T",
       rebase_path("//build/image/image.ld"),
-      "--defsym=ORIGIN_ADDRESS=${invoker.origin_address}",
     ]
     visibility = [ ":${invoker.target_name}" ]
   }
@@ -65,7 +61,7 @@
     ]
     args = [
       "--tool_prefix",
-      arch_tool_prefix,
+      tool_prefix,
       "--input",
       rebase_path(elf_file),
       "--output",
@@ -91,7 +87,6 @@
                              "testonly",
                            ])
     image_name = target_name
-    origin_address = arch_hypervisor_origin_address
   }
 }
 
@@ -110,7 +105,6 @@
                            ])
     output_path = "vm"
     image_name = target_name
-    origin_address = "0x1000"
   }
 }
 
diff --git a/build/toolchain/BUILD.gn b/build/toolchain/BUILD.gn
new file mode 100644
index 0000000..344d30e
--- /dev/null
+++ b/build/toolchain/BUILD.gn
@@ -0,0 +1,53 @@
+# Copyright 2018 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.
+
+import("//build/toolchain/embedded.gni")
+import("//build/toolchain/host.gni")
+
+host_toolchain("host") {
+  use_fake_arch = false
+}
+
+host_toolchain("host_fake") {
+  use_fake_arch = true
+  max_cpus = 4
+  max_vms = 6
+}
+
+aarch64_toolchain("qemu_aarch64") {
+  cpu = "cortex-a57+nofp"
+  origin_address = "0x40001000"
+  use_pl011 = true
+  pl011_base_address = "0x09000000"
+  max_cpus = 8
+  max_vms = 16
+}
+
+aarch64_toolchain("hikey") {
+  cpu = "cortex-a53+nofp"
+  origin_address = "0x01000000"
+  use_pl011 = true
+  pl011_base_address = "0xf8015000"
+  max_cpus = 8
+  max_vms = 16
+}
+
+aarch64_toolchain("aem_v8a_fvp") {
+  cpu = "cortex-a57+nofp"
+  origin_address = "0x88000000"
+  use_pl011 = true
+  pl011_base_address = "0x1c090000"
+  max_cpus = 8
+  max_vms = 16
+}
diff --git a/build/toolchain/arch/BUILD.gn b/build/toolchain/arch/BUILD.gn
deleted file mode 100644
index 6ce6207..0000000
--- a/build/toolchain/arch/BUILD.gn
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright 2018 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.
-
-# Target toolchain specific build arguments.
-declare_args() {
-  # Build with a specific compiler version e.g. when building with clang, set
-  # to "3.9" to build with `clang-3.9`.
-  arch_cc_version = ""
-}
-
-# Template for target toolchains; there is no support for C++ or libraries.
-# Instead, use source_set to group source together.
-template("cc_toolchain") {
-  toolchain(target_name) {
-    assert(defined(invoker.cc), "cc_toolchain() must specify a \"cc\" value")
-    assert(defined(invoker.ld), "cc_toolchain() must specify a \"ld\" value")
-
-    # Combine compiler version and compiler specific flags for this toolchain.
-    cc = "${invoker.cc}"
-    if (arch_cc_version != "") {
-      cc += "-${arch_cc_version}"
-    }
-    if (defined(invoker.cflags)) {
-      cc += " ${invoker.cflags}"
-    }
-
-    tool("cc") {
-      depfile = "{{output}}.d"
-      command = "${cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
-      depsformat = "gcc"
-      description = "CC {{output}}"
-      outputs = [
-        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
-      ]
-    }
-
-    tool("asm") {
-      depfile = "{{output}}.d"
-      command = "${cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
-      depsformat = "gcc"
-      description = "ASM {{output}}"
-      outputs = [
-        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
-      ]
-    }
-
-    tool("link") {
-      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
-      rspfile = "$outfile.rsp"
-      command = "${invoker.ld} {{ldflags}} -o $outfile --start-group @$rspfile --end-group"
-      description = "LINK $outfile"
-      default_output_dir = "{{root_out_dir}}"
-      rspfile_content = "{{inputs}}"
-      outputs = [
-        outfile,
-      ]
-    }
-
-    tool("stamp") {
-      command = "touch {{output}}"
-      description = "STAMP {{output}}"
-    }
-
-    tool("copy") {
-      command = "cp -af {{source}} {{output}}"
-      description = "COPY {{source}} {{output}}"
-    }
-
-    toolchain_args = {
-      forward_variables_from(invoker.toolchain_args, "*")
-    }
-  }
-}
-
-# Specialize for clang or gcc
-template("clang_toolchain") {
-  assert(defined(invoker.target),
-         "clang_toolchain() must specify a \"target\" value")
-  assert(defined(invoker.arch_tool_prefix),
-         "gcc_toolchain() must specify a \"arch_tool_prefix\" value")
-
-  cc_toolchain(target_name) {
-    cc = "clang"
-    cflags = "-target ${invoker.target} -fcolor-diagnostics"
-    ld = "ld.lld --color-diagnostics -O2 -lto-O2 --icf=all --fatal-warnings"
-
-    toolchain_args = {
-      arch_tool_prefix = invoker.arch_tool_prefix
-    }
-  }
-}
-
-template("gcc_toolchain") {
-  assert(defined(invoker.arch_tool_prefix),
-         "gcc_toolchain() must specify a \"arch_tool_prefix\" value")
-
-  cc_toolchain(target_name) {
-    cc = "${invoker.arch_tool_prefix}gcc"
-    cflags = "-fdiagnostics-color=always"
-    ld = "${invoker.arch_tool_prefix}ld"
-
-    toolchain_args = {
-      arch_tool_prefix = invoker.arch_tool_prefix
-    }
-  }
-}
-
-# Specialize for different architectures
-
-clang_toolchain("clang_aarch64") {
-  target = "aarch64-none-eabi"
-
-  # TODO: below isn't right for bare metal code, but it works fine
-  arch_tool_prefix = "aarch64-linux-gnu-"
-}
-
-gcc_toolchain("gcc_aarch64") {
-  # TODO: below isn't right for bare metal code, but it works fine
-  arch_tool_prefix = "aarch64-linux-gnu-"
-}
diff --git a/build/toolchain/embedded.gni b/build/toolchain/embedded.gni
new file mode 100644
index 0000000..b9a5cdd
--- /dev/null
+++ b/build/toolchain/embedded.gni
@@ -0,0 +1,248 @@
+# Copyright 2018 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.
+
+declare_args() {
+  # Set by arch toolchain. Prefix for binutils tools.
+  tool_prefix = ""
+}
+
+# Template for embedded toolchains; there is no support for C++ or libraries.
+# Instead, use source_set to group source together.
+template("embedded_cc_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.cc), "\"cc\" must be defined for ${target_name}.")
+    assert(defined(invoker.ld), "\"ld\" must be defined for ${target_name}.")
+
+    # Collect extra flags from the toolchain.
+    extra_defines = ""
+    extra_cflags = "-flto -ffunction-sections -fdata-sections"
+    extra_ldflags = "--gc-sections -pie"
+    if (defined(invoker.extra_defines)) {
+      extra_defines += " ${invoker.extra_defines}"
+    }
+    if (defined(invoker.extra_cflags)) {
+      extra_cflags += " ${invoker.extra_cflags}"
+    }
+    if (defined(invoker.extra_ldflags)) {
+      extra_ldflags += " ${invoker.extra_ldflags}"
+    }
+
+    # Define the tools.
+    tool("cc") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cc} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} ${extra_cflags} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("asm") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cc} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "ASM {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("link") {
+      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = "$outfile.rsp"
+      command = "${invoker.ld} ${extra_ldflags} {{ldflags}} -o $outfile --start-group @$rspfile --end-group"
+      description = "LINK $outfile"
+      default_output_dir = "{{root_out_dir}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        outfile,
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy") {
+      command = "cp -af {{source}} {{output}}"
+      description = "COPY {{source}} {{output}}"
+    }
+
+    toolchain_args = {
+      forward_variables_from(invoker.toolchain_args, "*")
+    }
+  }
+}
+
+# Specialize for clang.
+template("embedded_clang_toolchain") {
+  assert(defined(invoker.target),
+         "\"target\" must be defined for ${target_name}.")
+  assert(defined(invoker.tool_prefix),
+         "\"tool_prefix\" must be defined for ${target_name}.")
+
+  embedded_cc_toolchain(target_name) {
+    cc = "clang -target ${invoker.target} -fcolor-diagnostics"
+    ld = "ld.lld --color-diagnostics"
+
+    extra_defines = ""
+    extra_cflags = ""
+    extra_ldflags = "-O2 -lto-O2 --icf=all --fatal-warnings"
+    if (defined(invoker.extra_defines)) {
+      extra_defines += " ${invoker.extra_defines}"
+    }
+    if (defined(invoker.extra_cflags)) {
+      extra_cflags += " ${invoker.extra_cflags}"
+    }
+    if (defined(invoker.extra_ldflags)) {
+      extra_ldflags += " ${invoker.extra_ldflags}"
+    }
+
+    toolchain_args = {
+      tool_prefix = invoker.tool_prefix
+      if (defined(invoker.toolchain_args)) {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
+  }
+}
+
+# Specialize for gcc.
+template("embedded_gcc_toolchain") {
+  assert(defined(invoker.tool_prefix),
+         "\"tool_prefix\" must be defined for ${target_name}.")
+
+  embedded_cc_toolchain(target_name) {
+    cc = "${invoker.tool_prefix}gcc -fdiagnostics-color=always"
+    ld = "${invoker.tool_prefix}ld"
+
+    extra_defines = ""
+    extra_cflags = ""
+    extra_ldflags = ""
+    if (defined(invoker.extra_defines)) {
+      extra_defines += " ${invoker.extra_defines}"
+    }
+    if (defined(invoker.extra_cflags)) {
+      extra_cflags += " ${invoker.extra_cflags}"
+    }
+    if (defined(invoker.extra_ldflags)) {
+      extra_ldflags += " ${invoker.extra_ldflags}"
+    }
+
+    toolchain_args = {
+      tool_prefix = invoker.tool_prefix
+      if (defined(invoker.toolchain_args)) {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
+  }
+}
+
+# Expand to clang and gcc variants.
+template("embedded_platform_toolchain") {
+  assert(defined(invoker.arch), "\"arch\" must be defined for ${target_name}.")
+  assert(defined(invoker.target),
+         "\"target\" must be defined for ${target_name}.")
+  assert(defined(invoker.tool_prefix),
+         "\"tool_prefix\" must be defined for ${target_name}.")
+  assert(defined(invoker.origin_address),
+         "\"origin_address\" must be defined for ${target_name}.")
+  assert(defined(invoker.max_cpus),
+         "\"max_cpus\" must be defined for ${target_name}.")
+  assert(defined(invoker.max_vms),
+         "\"max_vms\" must be defined for ${target_name}.")
+
+  defines = ""
+  cflags = "-fno-stack-protector -fno-builtin -ffreestanding -fpic"
+  ldflags = "--defsym=ORIGIN_ADDRESS=${invoker.origin_address}"
+  if (defined(invoker.extra_defines)) {
+    defines += " ${invoker.extra_defines}"
+  }
+  if (defined(invoker.extra_cflags)) {
+    cflags += " ${invoker.extra_cflags}"
+  }
+  if (defined(invoker.extra_ldflags)) {
+    ldflags += " ${invoker.extra_ldflags}"
+  }
+
+  embedded_clang_toolchain("${target_name}_clang") {
+    target = invoker.target
+    tool_prefix = invoker.tool_prefix
+    extra_defines = defines
+    extra_cflags = cflags
+    extra_ldflags = ldflags
+    toolchain_args = {
+      platform_max_cpus = invoker.max_cpus
+      platform_max_vms = invoker.max_vms
+      arch = invoker.arch
+      if (defined(invoker.toolchain_args)) {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
+  }
+
+  embedded_gcc_toolchain("${target_name}_gcc") {
+    tool_prefix = invoker.tool_prefix
+    extra_defines = defines
+    extra_cflags = cflags
+    extra_ldflags = ldflags
+    toolchain_args = {
+      platform_max_cpus = invoker.max_cpus
+      platform_max_vms = invoker.max_vms
+      arch = invoker.arch
+      if (defined(invoker.toolchain_args)) {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
+  }
+}
+
+# Specialize for different architectures.
+
+template("aarch64_toolchain") {
+  assert(defined(invoker.cpu), "\"cpu\" must be defiend for ${target_name}.")
+  assert(defined(invoker.origin_address),
+         "\"origin_address\" must be defined for ${target_name}.")
+  assert(defined(invoker.use_pl011),
+         "\"use_pl011\" must be defined for ${target_name}.")
+  assert(defined(invoker.max_cpus),
+         "\"max_cpus\" must be defined for ${target_name}.")
+  assert(defined(invoker.max_vms),
+         "\"max_vms\" must be defined for ${target_name}.")
+
+  embedded_platform_toolchain(target_name) {
+    forward_variables_from(invoker,
+                           [
+                             "origin_address",
+                             "max_cpus",
+                             "max_vms",
+                           ])
+    arch = "aarch64"
+    target = "aarch64-none-eabi"
+    tool_prefix = "aarch64-linux-gnu-"  # TODO: this isn't right for bare metal but it works.
+    extra_cflags = "-mcpu=${invoker.cpu} -mstrict-align"
+
+    if (invoker.use_pl011) {
+      assert(defined(invoker.pl011_base_address),
+             "\"pl011_base_address\" must be defined for ${target_name}.")
+      extra_defines = "-DPL011_BASE=${invoker.pl011_base_address}"
+    }
+
+    toolchain_args = {
+      arch_aarch64_use_pl011 = invoker.use_pl011
+    }
+  }
+}
diff --git a/build/toolchain/host.gni b/build/toolchain/host.gni
new file mode 100644
index 0000000..e2f8913
--- /dev/null
+++ b/build/toolchain/host.gni
@@ -0,0 +1,176 @@
+# Copyright 2018 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.
+
+# Template for host toolchains.
+template("host_cc_toolchain") {
+  toolchain(target_name) {
+    assert(defined(invoker.ar), "\"ar\" must be defined for ${target_name}.")
+    assert(defined(invoker.cc), "\"cc\" must be defined for ${target_name}.")
+    assert(defined(invoker.cxx), "\"cxx\" must be defined for ${target_name}.")
+
+    # Collect extra flags from the toolchain.
+    extra_defines = ""
+    extra_cflags = ""
+    extra_ldflags = ""
+    if (defined(invoker.extra_defines)) {
+      extra_defines += " ${invoker.extra_defines}"
+    }
+    if (defined(invoker.extra_cflags)) {
+      extra_cflags += " ${invoker.extra_cflags}"
+    }
+    if (defined(invoker.extra_ldflags)) {
+      extra_ldflags += " ${invoker.extra_ldflags}"
+    }
+
+    tool("cc") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cc} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} ${extra_cflags} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CC {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("cxx") {
+      depfile = "{{output}}.d"
+      command = "${invoker.cxx} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} ${extra_cflags} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+      depsformat = "gcc"
+      description = "CXX {{output}}"
+      outputs = [
+        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+      ]
+    }
+
+    tool("alink") {
+      rspfile = "{{output}}.rsp"
+      command = "rm -f {{output}} && ${invoker.ar} rcs {{output}} @$rspfile"
+      description = "AR {{target_output_name}}{{output_extension}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        "{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
+      ]
+      default_output_extension = ".a"
+      output_prefix = "lib"
+    }
+
+    tool("solink") {
+      soname = "{{target_output_name}}{{output_extension}}"  # e.g. "libfoo.so".
+      sofile = "{{output_dir}}/$soname"
+      rspfile = soname + ".rsp"
+
+      command = "${invoker.cxx} -shared ${extra_ldflags} {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
+      rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
+
+      description = "SOLINK $soname"
+
+      # Use this for {{output_extension}} expansions unless a target manually
+      # overrides it (in which case {{output_extension}} will be what the target
+      # specifies).
+      default_output_extension = ".so"
+
+      # Use this for {{output_dir}} expansions unless a target manually overrides
+      # it (in which case {{output_dir}} will be what the target specifies).
+      default_output_dir = "{{root_out_dir}}"
+
+      outputs = [
+        sofile,
+      ]
+      link_output = sofile
+      depend_output = sofile
+      output_prefix = "lib"
+    }
+
+    tool("link") {
+      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
+      rspfile = "$outfile.rsp"
+      command = "${invoker.cxx} ${extra_ldflags} {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
+      description = "LINK $outfile"
+      default_output_dir = "{{root_out_dir}}"
+      rspfile_content = "{{inputs}}"
+      outputs = [
+        outfile,
+      ]
+    }
+
+    tool("stamp") {
+      command = "touch {{output}}"
+      description = "STAMP {{output}}"
+    }
+
+    tool("copy") {
+      command = "cp -af {{source}} {{output}}"
+      description = "COPY {{source}} {{output}}"
+    }
+
+    if (defined(invoker.toolchain_args)) {
+      toolchain_args = {
+        forward_variables_from(invoker.toolchain_args, "*")
+      }
+    }
+  }
+}
+
+template("host_toolchain") {
+  assert(defined(invoker.use_fake_arch),
+         "\"use_fake_arch\" must be defined for ${target_name}.")
+  if (invoker.use_fake_arch) {
+    assert(defined(invoker.max_cpus),
+           "\"max_cpus\" must be defined for ${target_name}.")
+    assert(defined(invoker.max_vms),
+           "\"max_vms\" must be defined for ${target_name}.")
+  }
+
+  # Specialize for clang.
+  host_cc_toolchain("${target_name}_clang") {
+    ar = "llvm-ar"
+    cc = "clang -fcolor-diagnostics"
+    cxx = "clang++"
+
+    # TODO: remove the need for this
+    extra_defines = "-DPL011_BASE=0"
+
+    if (invoker.use_fake_arch) {
+      toolchain_args = {
+        platform_max_cpus = invoker.max_cpus
+        platform_max_vms = invoker.max_vms
+
+        # When building for the ${target_name}, use the fake architecture to make things
+        # testable.
+        arch = "fake"
+      }
+    }
+  }
+
+  # Specialize for gcc.
+  host_cc_toolchain("${target_name}_gcc") {
+    ar = "ar"
+    cc = "gcc -fdiagnostics-color=always"
+    cxx = "g++ -fdiagnostics-color=always"
+
+    # TODO: remove the need for this
+    extra_defines = "-DPL011_BASE=0"
+
+    if (invoker.use_fake_arch) {
+      toolchain_args = {
+        platform_max_cpus = invoker.max_cpus
+        platform_max_vms = invoker.max_vms
+
+        # When building for the ${target_name}, use the fake architecture to make things
+        # testable.
+        arch = "fake"
+      }
+    }
+  }
+}
diff --git a/build/toolchain/host/BUILD.gn b/build/toolchain/host/BUILD.gn
deleted file mode 100644
index dd344b7..0000000
--- a/build/toolchain/host/BUILD.gn
+++ /dev/null
@@ -1,169 +0,0 @@
-# Copyright 2018 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.
-
-# Host toolchain specific build arguments.
-declare_args() {
-  # Build with a specific compiler version e.g. when building with clang, set
-  # to "3.9" to build with `clang-3.9`.
-  host_cc_version = ""
-}
-
-# Template for host toolchains.
-template("cc_toolchain") {
-  toolchain(target_name) {
-    assert(defined(invoker.ar), "cc_toolchain() must specify a \"ar\" value")
-    assert(defined(invoker.cc), "cc_toolchain() must specify a \"cc\" value")
-    assert(defined(invoker.cxx), "cc_toolchain() must specify a \"cxx\" value")
-
-    cc = "${invoker.cc}"
-    cxx = "${invoker.cxx}"
-    if (host_cc_version != "") {
-      cc += "-${host_cc_version}"
-      cxx += "-${host_cc_version}"
-    }
-    if (defined(invoker.cflags)) {
-      cc += " ${invoker.cflags}"
-      cxx += " ${invoker.cflags}"
-    }
-
-    tool("cc") {
-      depfile = "{{output}}.d"
-      command = "${cc} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
-      depsformat = "gcc"
-      description = "CC {{output}}"
-      outputs = [
-        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
-      ]
-    }
-
-    tool("cxx") {
-      depfile = "{{output}}.d"
-      command = "${cxx} -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
-      depsformat = "gcc"
-      description = "CXX {{output}}"
-      outputs = [
-        "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
-      ]
-    }
-
-    tool("alink") {
-      rspfile = "{{output}}.rsp"
-      command = "rm -f {{output}} && ${invoker.ar} rcs {{output}} @$rspfile"
-      description = "AR {{target_output_name}}{{output_extension}}"
-      rspfile_content = "{{inputs}}"
-      outputs = [
-        "{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
-      ]
-      default_output_extension = ".a"
-      output_prefix = "lib"
-    }
-
-    tool("solink") {
-      soname = "{{target_output_name}}{{output_extension}}"  # e.g. "libfoo.so".
-      sofile = "{{output_dir}}/$soname"
-      rspfile = soname + ".rsp"
-
-      command =
-          "${cxx} -shared {{ldflags}} -o $sofile -Wl,-soname=$soname @$rspfile"
-      rspfile_content = "-Wl,--whole-archive {{inputs}} {{solibs}} -Wl,--no-whole-archive {{libs}}"
-
-      description = "SOLINK $soname"
-
-      # Use this for {{output_extension}} expansions unless a target manually
-      # overrides it (in which case {{output_extension}} will be what the target
-      # specifies).
-      default_output_extension = ".so"
-
-      # Use this for {{output_dir}} expansions unless a target manually overrides
-      # it (in which case {{output_dir}} will be what the target specifies).
-      default_output_dir = "{{root_out_dir}}"
-
-      outputs = [
-        sofile,
-      ]
-      link_output = sofile
-      depend_output = sofile
-      output_prefix = "lib"
-    }
-
-    tool("link") {
-      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
-      rspfile = "$outfile.rsp"
-      command = "${cxx} {{ldflags}} -o $outfile -Wl,--start-group @$rspfile {{solibs}} -Wl,--end-group {{libs}}"
-      description = "LINK $outfile"
-      default_output_dir = "{{root_out_dir}}"
-      rspfile_content = "{{inputs}}"
-      outputs = [
-        outfile,
-      ]
-    }
-
-    tool("stamp") {
-      command = "touch {{output}}"
-      description = "STAMP {{output}}"
-    }
-
-    tool("copy") {
-      command = "cp -af {{source}} {{output}}"
-      description = "COPY {{source}} {{output}}"
-    }
-
-    if (defined(invoker.toolchain_args)) {
-      toolchain_args = {
-        forward_variables_from(invoker.toolchain_args, "*")
-      }
-    }
-  }
-}
-
-# Specialize for clang or gcc
-cc_toolchain("clang") {
-  ar = "llvm-ar"
-  cc = "clang"
-  cxx = "clang++"
-  cflags = "-fcolor-diagnostics"
-}
-
-cc_toolchain("clang_fake_arch") {
-  ar = "llvm-ar"
-  cc = "clang"
-  cxx = "clang++"
-  cflags = "-fcolor-diagnostics"
-
-  toolchain_args = {
-    # When building for the host, use the fake architecture to make things
-    # testable.
-    arch = "fake"
-  }
-}
-
-cc_toolchain("gcc") {
-  ar = "ar"
-  cc = "gcc"
-  cxx = "g++"
-  cflags = "-fdiagnostics-color=always"
-}
-
-cc_toolchain("gcc_fake_arch") {
-  ar = "ar"
-  cc = "gcc"
-  cxx = "g++"
-  cflags = "-fdiagnostics-color=always"
-
-  toolchain_args = {
-    # When building for the host, use the fake architecture to make things
-    # testable.
-    arch = "fake"
-  }
-}
diff --git a/build/arch/aarch64.gni b/build/toolchain/platform.gni
similarity index 62%
copy from build/arch/aarch64.gni
copy to build/toolchain/platform.gni
index f9df8f2..7a1c8a9 100644
--- a/build/arch/aarch64.gni
+++ b/build/toolchain/platform.gni
@@ -12,14 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+# Configuration of the build for the platform.
 declare_args() {
-  # Whether to include the PrimeCell UART (PL011) driver.
-  arch_aarch64_use_pl011 = false
+  # The maximum number of CPUs available on the platform.
+  platform_max_cpus = 0
 
-  # The base address of the PrimeCell UART (PL011) device.
-  arch_aarch64_pl011_base_address = ""
+  # The maximum number of VMs required for the platform.
+  platform_max_vms = 0
 }
-
-assert(
-    !arch_aarch64_use_pl011 || arch_aarch64_pl011_base_address != "",
-    "Must provide the PL011 base address as \"arch_aarch64_pl011_base_address\".")
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index a538e2e..9a815a6 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -29,34 +29,23 @@
 sudo apt install make binutils-aarch64-linux-gnu
 ```
 
-By default, the hypervisor is built with clang for an aarch64 QEMU target by
-running:
+By default, the hypervisor is built with clang for a few target platforms along
+with tests.
 
 ``` shell
 make
 ```
 
-The compiled image can be found at `out/aarch64/qemu/clang_aarch64/hafnium.bin`.
+The compiled image can be found under `out/`, for example the QEMU image is at
+at `out/qemu_aarch64_clang/hafnium.bin`.
 
-To build for the HiKey board, change the target platform:
-
-``` shell
-PLATFORM=hikey make
-```
-
-To build using gcc instead of clang, the aarch64 variant must be installed:
-
-``` shell
-sudo apt install gcc-aarch64-linux-gnu
-GCC=true make
-```
 ## Running on QEMU
 
 You will need at least version 2.9 for QEMU. The following command line can be
 used to run Hafnium on it:
 
 ``` shell
-qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/aarch64/qemu/clang_aarch64/hafnium.bin
+qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/qemu_aarch64_clang/hafnium.bin
 ```
 
 Though it is admittedly not very useful because it doesn't have any virtual
@@ -67,7 +56,7 @@
 which will then boot into the primary Linux VM:
 
 ``` shell
-qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/aarch64/qemu/clang_aarch64/hafnium.bin -initrd initrd.img -append "rdinit=/sbin/init"
+qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/qemu_aarch64_clang/hafnium.bin -initrd initrd.img -append "rdinit=/sbin/init"
 ```
 
 ## Running tests
diff --git a/inc/hf/decl_offsets.h b/inc/hf/decl_offsets.h
deleted file mode 100644
index 704c42b..0000000
--- a/inc/hf/decl_offsets.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2018 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.
- */
-
-#pragma once
-
-#ifdef GEN_OFFSETS
-
-/* When generating offsets, create constants which can be extracted from the
- * generated assembly. */
-
-#define DECL(name, type, field) \
-	const size_t DEFINE_OFFSET__##name = offsetof(type, field)
-
-#define DECL_SIZE(name, type) const size_t DEFINE_OFFSET__name = sizeof(type)
-
-#else /* GEN_OFFSETS */
-
-/* When not generating offsets, validate that the extracted values are as
- * expected. */
-
-#include <assert.h>
-
-#define DECL(name, type, field) DECL_1(#name, name, offsetof(type, field))
-#define DECL_1(name, actual, expected)                       \
-	static_assert((actual) == (expected),                \
-		      "Offset " name " should be " #expected \
-		      " and not " #actual)
-
-#define DECL_SIZE(name, type) DECL_SIZE_1(#name, name, sizeof(type))
-#define DECL_SIZE_1(name, actual, expected)                 \
-	static_assert((actual) == (expected),               \
-		      "Size " #name " should be " #expected \
-		      " and not" #actual)
-
-#endif /* GEN_OFFSETS */
diff --git a/kokoro/ubuntu/build.sh b/kokoro/ubuntu/build.sh
index 472a14c..769ce46 100755
--- a/kokoro/ubuntu/build.sh
+++ b/kokoro/ubuntu/build.sh
@@ -38,13 +38,8 @@
 # Step 1: make sure it builds.
 #
 
-export ARCH=aarch64
-export PLATFORM=qemu
-
-# TODO: add a gcc-4.9 or above prebuilt to check the gcc build too?
 # Check the build works.
 make
-make check
 
 #
 # Step 2: make sure it works.
@@ -53,15 +48,45 @@
 ./kokoro/ubuntu/test.sh
 
 #
-# Step 3: make sure the code looks good.
+# Step 3: static analysis.
+#
+
+make check
+if [[ `git status --porcelain` ]]
+then
+	echo "Run \`make check\' locally to fix this."
+	exit 1
+fi
+
+#
+# Step 4: make sure the code looks good.
 #
 
 make format
-make tidy
-make license
-
 if [[ `git status --porcelain` ]]
 then
-	echo "Run \`make format\', \`make tidy\` and \`make license\` locally to fix this."
+	echo "Run \`make format\' locally to fix this."
+	exit 1
+fi
+
+#
+# Step 5: make sure there's not lint.
+#
+
+make tidy
+if [[ `git status --porcelain` ]]
+then
+	echo "Run \`make tidy\' locally to fix this."
+	exit 1
+fi
+
+#
+# Step 6: make sure all the files have a license.
+#
+
+make license
+if [[ `git status --porcelain` ]]
+then
+	echo "Run \`make license\' locally to fix this."
 	exit 1
 fi
diff --git a/kokoro/ubuntu/continuous.cfg b/kokoro/ubuntu/continuous.cfg
index 7282715..538e2a2 100644
--- a/kokoro/ubuntu/continuous.cfg
+++ b/kokoro/ubuntu/continuous.cfg
@@ -5,8 +5,8 @@
 
 action {
   define_artifacts {
-    regex: "git/hafnium/out/**/test_log/**/*sponge_log.log"
-    regex: "git/hafnium/out/**/test_log/**/*sponge_log.xml"
+    regex: "git/hafnium/out/kokoro_log/**/*sponge_log.log"
+    regex: "git/hafnium/out/kokoro_log/**/*sponge_log.xml"
     strip_prefix: "git/hafnium"
   }
 }
diff --git a/kokoro/ubuntu/presubmit.cfg b/kokoro/ubuntu/presubmit.cfg
index 71e3719..7841ddf 100644
--- a/kokoro/ubuntu/presubmit.cfg
+++ b/kokoro/ubuntu/presubmit.cfg
@@ -5,8 +5,8 @@
 
 action {
   define_artifacts {
-    regex: "git/hafnium/out/**/test_log/**/*sponge_log.log"
-    regex: "git/hafnium/out/**/test_log/**/*sponge_log.xml"
+    regex: "git/hafnium/out/kokoro_log/**/*sponge_log.log"
+    regex: "git/hafnium/out/kokoro_log/**/*sponge_log.xml"
     strip_prefix: "git/hafnium"
   }
 }
diff --git a/kokoro/ubuntu/test.sh b/kokoro/ubuntu/test.sh
index 3c22c94..f3ac332 100755
--- a/kokoro/ubuntu/test.sh
+++ b/kokoro/ubuntu/test.sh
@@ -27,14 +27,14 @@
 set -x
 
 TIMEOUT="timeout --foreground"
-OUT="out/aarch64/qemu"
-HFTEST="$TIMEOUT 30s ./test/vm/hftest.py --out $OUT/clang_aarch64 --initrd"
+OUT="out"
+HFTEST="$TIMEOUT 30s ./test/vm/hftest.py --out $OUT/qemu_aarch64_clang --log $OUT/kokoro_log --initrd"
 
 # Run the host unit tests
-mkdir -p $OUT/clang_fake_arch/test_log/unit_tests
-$TIMEOUT 30s $OUT/clang_fake_arch/unit_tests \
-  --gtest_output="xml:$OUT/clang_fake_arch/test_log/unit_tests/sponge_log.xml" \
-  | tee $OUT/clang_fake_arch/test_log/unit_tests/sponge_log.log
+mkdir -p $OUT/kokoro_log/unit_tests
+$TIMEOUT 30s $OUT/host_fake_clang/unit_tests \
+  --gtest_output="xml:$OUT/kokoro_log/unit_tests/sponge_log.xml" \
+  | tee $OUT/kokoro_log/unit_tests/sponge_log.log
 
 # Run the tests with a timeout so they can't loop forever.
 $HFTEST primary_only_test
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 0b4ea31..008315a 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -12,14 +12,27 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Hypervisor specific code.
-source_set("src") {
+import("//build/image/image.gni")
+
+# The hypervisor image.
+hypervisor("hafnium") {
+  sources = [
+    "layout.c",
+  ]
+  deps = [
+    ":src_not_testable_yet",
+    "//src/arch/${arch}",
+  ]
+}
+
+# Hypervisor specific code that isn't. One day it will be testable and both the
+# src targets will merge!
+source_set("src_not_testable_yet") {
   sources = [
     "cpio.c",
     "load.c",
     "main.c",
   ]
-  sources += [ "layout.c" ]
   deps = [
     ":src_testable",
   ]
diff --git a/src/arch/aarch64/BUILD.gn b/src/arch/aarch64/BUILD.gn
index 520a97d..1588e18 100644
--- a/src/arch/aarch64/BUILD.gn
+++ b/src/arch/aarch64/BUILD.gn
@@ -12,8 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import("//build/arch/aarch64.gni")
-import("//build/image/generate_offsets.gni")
+import("args.gni")
 
 # Hypervisor specific code.
 source_set("aarch64") {
@@ -33,14 +32,6 @@
 
   deps = [
     ":entry",
-    ":offsets",
-  ]
-}
-
-# Calculate struct field offsets for hypervisor_entry.S.
-generate_offsets("offsets") {
-  sources = [
-    "offsets.c",
   ]
 }
 
diff --git a/build/arch/aarch64.gni b/src/arch/aarch64/args.gni
similarity index 71%
rename from build/arch/aarch64.gni
rename to src/arch/aarch64/args.gni
index f9df8f2..eabe6d4 100644
--- a/build/arch/aarch64.gni
+++ b/src/arch/aarch64/args.gni
@@ -15,11 +15,4 @@
 declare_args() {
   # Whether to include the PrimeCell UART (PL011) driver.
   arch_aarch64_use_pl011 = false
-
-  # The base address of the PrimeCell UART (PL011) device.
-  arch_aarch64_pl011_base_address = ""
 }
-
-assert(
-    !arch_aarch64_use_pl011 || arch_aarch64_pl011_base_address != "",
-    "Must provide the PL011 base address as \"arch_aarch64_pl011_base_address\".")
diff --git a/src/arch/aarch64/offsets.c b/src/arch/aarch64/offsets.c
index 558b956..2cf5be8 100644
--- a/src/arch/aarch64/offsets.c
+++ b/src/arch/aarch64/offsets.c
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
-#ifndef GEN_OFFSETS
 #include "offsets.h"
-#endif /* GEN_OFFSETS */
+
+#include <assert.h>
 
 #include "hf/cpu.h"
-#include "hf/decl_offsets.h"
 
-DECL(CPU_STACK_BOTTOM, struct cpu, stack_bottom);
-DECL(VCPU_REGS, struct vcpu, regs);
-DECL(VCPU_LAZY, struct vcpu, regs.lazy);
+#define CHECK_OFFSET(name, type, field) \
+	CHECK_OFFSET_1(#name, name, offsetof(type, field))
+#define CHECK_OFFSET_1(name, actual, expected)               \
+	static_assert((actual) == (expected),                \
+		      "Offset " name " should be " #expected \
+		      " and not " #actual)
+
+CHECK_OFFSET(CPU_STACK_BOTTOM, struct cpu, stack_bottom);
+CHECK_OFFSET(VCPU_REGS, struct vcpu, regs);
+CHECK_OFFSET(VCPU_LAZY, struct vcpu, regs.lazy);
diff --git a/src/arch/aarch64/offsets.h b/src/arch/aarch64/offsets.h
new file mode 100644
index 0000000..e967fe4
--- /dev/null
+++ b/src/arch/aarch64/offsets.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018 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.
+ */
+
+#pragma once
+
+/* These are checked in offset.c. */
+#define CPU_STACK_BOTTOM 8
+#define VCPU_REGS 32
+#define VCPU_LAZY (VCPU_REGS + 264)
diff --git a/src/arch/fake/inc/hf/arch/mm.h b/src/arch/fake/inc/hf/arch/mm.h
index c99bf92..8033f46 100644
--- a/src/arch/fake/inc/hf/arch/mm.h
+++ b/src/arch/fake/inc/hf/arch/mm.h
@@ -38,7 +38,6 @@
 typedef uint64_t pte_t;
 
 #define PAGE_LEVEL_BITS 9
-#define PL011_BASE 0x11
 
 /**
  * Returns the encoding of a page table entry that isn't present.
diff --git a/src/cpu.c b/src/cpu.c
index 4c6a18b..b9de38d 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -27,6 +27,8 @@
 
 #include "vmapi/hf/call.h"
 
+#define STACK_SIZE PAGE_SIZE
+
 /* The stack to be used by the CPUs. */
 alignas(2 * sizeof(uintreg_t)) static char callstacks[MAX_CPUS][STACK_SIZE];
 
diff --git a/test/vm/BUILD.gn b/test/vm/BUILD.gn
index f66a698..9442547 100644
--- a/test/vm/BUILD.gn
+++ b/test/vm/BUILD.gn
@@ -14,8 +14,19 @@
 
 import("//build/image/image.gni")
 
+group("vm_tests") {
+  testonly = true
+
+  deps = [
+    ":primary_only_test",
+    ":primary_with_secondaries_test",
+  ]
+}
+
 # Primary VMs host the test framework.
 source_set("hftest_vm") {
+  testonly = true
+
   sources = [
     "hftest.c",
     "hftest.h",
@@ -35,6 +46,8 @@
 
 # Secondary VMs can be used as part of a test in the primary.
 source_set("secondary_vm") {
+  testonly = true
+
   deps = [
     "//src:common",
     "//src:dlog",
@@ -60,6 +73,7 @@
 
 initrd("primary_only_test") {
   testonly = true
+
   primary_vm = ":primary_only_test_vm"
 }
 
@@ -78,6 +92,7 @@
 
 initrd("primary_with_secondaries_test") {
   testonly = true
+
   primary_vm = ":primary_with_secondaries_test_vm"
   secondary_vms = [
     [
diff --git a/test/vm/hftest.py b/test/vm/hftest.py
index 91e3bcc..92e85e0 100755
--- a/test/vm/hftest.py
+++ b/test/vm/hftest.py
@@ -72,6 +72,7 @@
 def Main():
     parser = argparse.ArgumentParser()
     parser.add_argument("--out", required=True)
+    parser.add_argument("--log", required=True)
     parser.add_argument("--initrd", required=True)
     parser.add_argument("--suite")
     parser.add_argument("--test")
@@ -79,7 +80,7 @@
     # Resolve some paths.
     hafnium = os.path.join(args.out, "hafnium.bin")
     initrd = os.path.join(args.out, "initrd", args.initrd + ".img")
-    log = os.path.join(args.out, "test_log", args.initrd)
+    log = os.path.join(args.log, args.initrd)
     ensure_dir(log)
     print("Logs saved under", log)
     log_file = os.path.join(log, "sponge_log.log")