Merge #268
268: Add the libtock_runtime crate and platform switching. r=hudson-ayers a=jrvanwhy
This is the first `libtock_runtime` PR. It contains:
A. `Cargo.toml` for `libtock_runtime`
B. Automatic linker script identification.
The automatic linker script identification is based on `libtock-rs`'s current `build.rs` (in the root of the repository), but makes the following improvements:
A. Better supports out-of-tree platforms by adding a cargo feature to disable linker script identification. `libtock-rs`'s build.rs always emits a warning of the PLATFORM variable is not specified.
B. Improves error handling, particularly around builds in paths we cannot support (e.g. if `cargo` is executed in a directory whose name contains a newline character).
C. Moves the layout file location into `cargo`'s `OUT_DIR`, which is the only directory build scripts are *supposed* to modify.
D. Removes support for the `platform` file to select a platform. `libtock-rs` looked for a `platform` file in its own source to identify the platform it is run in -- I'm not really sure how this was supposed to be used.
Co-authored-by: Johnathan Van Why <jrvanwhy@google.com>
diff --git a/.gitignore b/.gitignore
index ae858ab..2bcb93f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
/Cargo.lock
-/layout.ld
/platform
/target
diff --git a/Cargo.toml b/Cargo.toml
index 99543d9..89185d4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -83,6 +83,7 @@
"codegen",
"core",
"core/platform",
+ "core/runtime",
"test_runner",
"tools/print_sizes",
]
diff --git a/Makefile b/Makefile
index 779a021..da40d28 100644
--- a/Makefile
+++ b/Makefile
@@ -78,9 +78,9 @@
.PHONY: test
test: examples test-qemu-hifive
- PLATFORM=nrf52 cargo fmt --all -- --check
- PLATFORM=nrf52 cargo clippy --workspace --all-targets
- PLATFORM=nrf52 cargo miri test --workspace
+ LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo fmt --all -- --check
+ LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo clippy --workspace --all-targets
+ LIBTOCK_PLATFORM=nrf52 PLATFORM=nrf52 cargo miri test --workspace
echo '[ SUCCESS ] libtock-rs tests pass'
.PHONY: analyse-stack-sizes
diff --git a/build.rs b/build.rs
index b24d3d2..dc2dbef 100644
--- a/build.rs
+++ b/build.rs
@@ -3,10 +3,11 @@
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
-use std::path::Path;
+use std::path::{Path, PathBuf};
use std::process;
static LAYOUT_FILE_NAME: &str = "layout.ld";
+static LAYOUT_GENERIC_FILENAME: &str = "layout_generic.ld";
fn main() {
static PLATFORM_ENV_VAR: &str = "PLATFORM";
@@ -19,6 +20,7 @@
println!("cargo:rerun-if-env-changed={}", KERNEL_HEAP_SIZE);
println!("cargo:rerun-if-changed={}", PLATFORM_FILE_NAME);
println!("cargo:rerun-if-changed={}", LAYOUT_FILE_NAME);
+ println!("cargo:rerun-if-changed={}", LAYOUT_GENERIC_FILENAME);
let platform_name =
read_env_var(PLATFORM_ENV_VAR).or_else(|| read_board_name_from_file(PLATFORM_FILE_NAME));
@@ -70,5 +72,21 @@
println!("Cannot find layout file {:?}", path);
process::exit(1);
}
- fs::copy(linker_file_name, LAYOUT_FILE_NAME).unwrap();
+ // Note: cargo fails if run in a path that is not valid Unicode, so this
+ // script doesn't need to handle non-Unicode paths. Also, OUT_DIR cannot be
+ // in a location with a newline in it, or we have no way to pass
+ // rustc-link-search to cargo.
+ let out_dir = &std::env::var("OUT_DIR").expect("Unable to read OUT_DIR");
+ assert!(
+ !out_dir.contains('\n'),
+ "Build path contains a newline, which is unsupported"
+ );
+ let out_layout_path: PathBuf = [out_dir, "layout.ld"].iter().collect();
+ fs::copy(linker_file_name, out_layout_path).unwrap();
+
+ // Copy the generic layout file into OUT_DIR.
+ let out_layout_generic: PathBuf = [out_dir, LAYOUT_GENERIC_FILENAME].iter().collect();
+ fs::copy(LAYOUT_GENERIC_FILENAME, out_layout_generic)
+ .expect("Unable to copy layout_generic.ld into OUT_DIR");
+ println!("cargo:rustc-link-search={}", out_dir);
}
diff --git a/core/examples/empty_main.rs b/core/examples/empty_main.rs
deleted file mode 100644
index a3644b7..0000000
--- a/core/examples/empty_main.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-// The most minimal libtock_core example possible. This file primarily exists
-// for code size measurement, as this should create the smallest-possible
-// libtock_core app.
-
-#![no_std]
-
-// If you don't *use* anything from libtock_core directly, cargo will not link
-// it into the executable. However, we still need the runtime and lang items.
-// Therefore a libtock_core app that doesn't directly mention anything in
-// libtock_core needs to explicitly declare its dependency on libtock_core as
-// follows.
-extern crate libtock_core;
-
-libtock_core::stack_size! {0x400}
-
-fn main() {}
diff --git a/core/runtime/Cargo.toml b/core/runtime/Cargo.toml
new file mode 100644
index 0000000..8da4f70
--- /dev/null
+++ b/core/runtime/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+authors = ["Tock Project Developers <tock-dev@googlegroups.com>"]
+categories = ["embedded", "no-std", "os"]
+description = """libtock-rs runtime. Provides raw system call implementations \
+ and language items necessary for Tock apps."""
+edition = "2018"
+license = "Apache-2.0 OR MIT"
+name = "libtock_runtime"
+repository = "https://www.github.com/tock/libtock-rs"
+version = "0.1.0"
+
+[dependencies]
+libtock_platform = { path = "../platform" }
+
+[features]
+# By default, libtock_runtime looks for the LIBTOCK_PLATFORM variable to decide
+# what layout file to use. If you are providing your own linker script, set
+# no_auto_layout to disable the layout file logic.
+no_auto_layout = []
diff --git a/core/runtime/build.rs b/core/runtime/build.rs
new file mode 100644
index 0000000..d0281b5
--- /dev/null
+++ b/core/runtime/build.rs
@@ -0,0 +1,55 @@
+use std::fs::copy;
+use std::path::PathBuf;
+
+// auto_layout() identifies the correct linker scripts to use based on the
+// LIBTOCK_PLATFORM environment variable, and copies the linker scripts into
+// OUT_DIR. The cargo invocation must pass -C link-arg=-Tlayout.ld to rustc
+// (using the rustflags cargo config).
+#[cfg(not(feature = "no_auto_layout"))]
+fn auto_layout(out_dir: &str) {
+ const PLATFORM_CFG_VAR: &str = "LIBTOCK_PLATFORM";
+ const LAYOUT_GENERIC_FILENAME: &str = "layout_generic.ld";
+
+ // Note: we need to print these rerun-if commands before using the variable
+ // or file, so that if the build script fails cargo knows when to re-run it.
+ println!("cargo:rerun-if-env-changed={}", PLATFORM_CFG_VAR);
+
+ // Read configuration from environment variables.
+
+ // Read the platform environment variable as a String (our platform names
+ // should all be valid UTF-8).
+ let platform = std::env::var(PLATFORM_CFG_VAR).expect("Please specify LIBTOCK_PLATFORM");
+
+ // Copy the platform-specific layout file into OUT_DIR.
+ let platform_filename = format!("{}.ld", platform);
+ let platform_path: PathBuf = ["layouts", &platform_filename].iter().collect();
+ println!("cargo:rerun-if-changed={}", platform_path.display());
+ assert!(platform_path.exists(), "Unknown platform {}", platform);
+ let out_platform_path: PathBuf = [out_dir, "layout.ld"].iter().collect();
+ copy(&platform_path, out_platform_path).expect("Unable to copy platform layout into OUT_DIR");
+
+ // Copy the generic layout file into OUT_DIR.
+ let out_layout_generic: PathBuf = [out_dir, LAYOUT_GENERIC_FILENAME].iter().collect();
+ println!("cargo:rerun-if-changed={}", LAYOUT_GENERIC_FILENAME);
+ copy(LAYOUT_GENERIC_FILENAME, out_layout_generic)
+ .expect("Unable to copy layout_generic.ld into OUT_DIR");
+}
+
+fn main() {
+ // Note: cargo fails if run in a path that is not valid Unicode, so this
+ // script doesn't need to handle non-Unicode paths. Also, OUT_DIR cannot be
+ // in a location with a newline in it, or we have no way to pass
+ // rustc-link-search to cargo.
+ let out_dir = &std::env::var("OUT_DIR").expect("Unable to read OUT_DIR");
+ assert!(
+ !out_dir.contains('\n'),
+ "Build path contains a newline, which is unsupported"
+ );
+
+ #[cfg(not(feature = "no_auto_layout"))]
+ auto_layout(out_dir);
+
+ // This link search path is used by both auto_layout() and extern_asm().
+ // TODO: Add external assembly and extern_asm().
+ println!("cargo:rustc-link-search={}", out_dir);
+}
diff --git a/core/runtime/layout_generic.ld b/core/runtime/layout_generic.ld
new file mode 100644
index 0000000..0bbef87
--- /dev/null
+++ b/core/runtime/layout_generic.ld
@@ -0,0 +1,171 @@
+/* Userland Generic Layout
+ *
+ * Currently, due to incomplete ROPI-RWPI support in rustc (see
+ * https://github.com/tock/libtock-rs/issues/28), this layout implements static
+ * linking. An application init script must define the FLASH and SRAM address
+ * ranges as well as MPU_MIN_ALIGN before including this layout file.
+ *
+ * Here is a an example application linker script to get started:
+ * MEMORY {
+ * /* FLASH memory region must start immediately *after* the Tock
+ * * Binary Format headers, which means you need to offset the
+ * * beginning of FLASH memory region relative to where the
+ * * application is loaded.
+ * FLASH (rx) : ORIGIN = 0x10030, LENGTH = 0x0FFD0
+ * SRAM (RWX) : ORIGIN = 0x20000, LENGTH = 0x10000
+ * }
+ * MPU_MIN_ALIGN = 8K;
+ * INCLUDE ../libtock-rs/layout.ld
+ */
+
+ENTRY(_start)
+
+SECTIONS {
+ /* Section for just the app crt0 header.
+ * This must be first so that the app can find it.
+ */
+ .crt0_header :
+ {
+ _beginning = .; /* Start of the app in flash. */
+ /**
+ * Populate the header expected by `crt0`:
+ *
+ * struct hdr {
+ * uint32_t got_sym_start;
+ * uint32_t got_start;
+ * uint32_t got_size;
+ * uint32_t data_sym_start;
+ * uint32_t data_start;
+ * uint32_t data_size;
+ * uint32_t bss_start;
+ * uint32_t bss_size;
+ * uint32_t reldata_start;
+ * uint32_t stack_size;
+ * };
+ */
+ /* Offset of GOT symbols in flash */
+ LONG(LOADADDR(.got) - _beginning);
+ /* Offset of GOT section in memory */
+ LONG(_got);
+ /* Size of GOT section */
+ LONG(SIZEOF(.got));
+ /* Offset of data symbols in flash */
+ LONG(LOADADDR(.data) - _beginning);
+ /* Offset of data section in memory */
+ LONG(_data);
+ /* Size of data section */
+ LONG(SIZEOF(.data));
+ /* Offset of BSS section in memory */
+ LONG(_bss);
+ /* Size of BSS section */
+ LONG(SIZEOF(.bss));
+ /* First address offset after program flash, where elf2tab places
+ * .rel.data section */
+ LONG(LOADADDR(.endflash) - _beginning);
+ /* The size of the stack requested by this application */
+ LONG(_stack_top_aligned - _sstack);
+ /* Pad the header out to a multiple of 32 bytes so there is not a gap
+ * between the header and subsequent .data section. It's unclear why,
+ * but LLD is aligning sections to a multiple of 32 bytes. */
+ . = ALIGN(32);
+ } > FLASH =0xFF
+
+ /* Text section, Code! */
+ .text :
+ {
+ . = ALIGN(4);
+ _text = .;
+ KEEP (*(.start))
+ *(.text*)
+ *(.rodata*)
+ KEEP (*(.syscalls))
+ *(.ARM.extab*)
+ . = ALIGN(4); /* Make sure we're word-aligned here */
+ _etext = .;
+ } > FLASH =0xFF
+
+ /* Application stack */
+ .stack (NOLOAD) :
+ {
+ /* elf2tab requires that the `_sram_origin` symbol be present to
+ * mark the first address in the SRAM memory. Since ELF files do
+ * not really need to specify this address as they only care about
+ * loading into flash, we need to manually mark this address for
+ * elf2tab. elf2tab will use it to add a fixed address header in the
+ * TBF header if needed.
+ */
+ _sram_origin = .;
+ _sstack = .;
+ KEEP(*(.stack_buffer))
+ _stack_top_unaligned = .;
+ . = ALIGN(8);
+ _stack_top_aligned = .;
+ } > SRAM
+
+ /* Data section, static initialized variables
+ * Note: This is placed in Flash after the text section, but needs to be
+ * moved to SRAM at runtime
+ */
+ .data : AT (_etext)
+ {
+ . = ALIGN(4); /* Make sure we're word-aligned here */
+ _data = .;
+ KEEP(*(.data*))
+ *(.sdata*) /* RISC-V small-pointer data section */
+ . = ALIGN(4); /* Make sure we're word-aligned at the end of flash */
+ } > SRAM
+
+ /* Global Offset Table */
+ .got :
+ {
+ . = ALIGN(4); /* Make sure we're word-aligned here */
+ _got = .;
+ *(.got*)
+ *(.got.plt*)
+ . = ALIGN(4);
+ } > SRAM
+
+ /* BSS section, static uninitialized variables */
+ .bss :
+ {
+ . = ALIGN(4); /* Make sure we're word-aligned here */
+ _bss = .;
+ KEEP(*(.bss* .sbss*))
+ *(COMMON)
+ . = ALIGN(4);
+ } > SRAM
+
+ /* End of flash. */
+ .endflash :
+ {
+ } > FLASH
+
+ /* ARM Exception support
+ *
+ * This contains compiler-generated support for unwinding the stack,
+ * consisting of key-value pairs of function addresses and information on
+ * how to unwind stack frames.
+ * https://wiki.linaro.org/KenWerner/Sandbox/libunwind?action=AttachFile&do=get&target=libunwind-LDS.pdf
+ *
+ * .ARM.exidx is sorted, so has to go in its own output section.
+ *
+ * __NOTE__: It's at the end because we currently don't actually serialize
+ * it to the binary in elf2tbf. If it was before the RAM sections, it would
+ * through off our calculations of the header.
+ */
+ PROVIDE_HIDDEN (__exidx_start = .);
+ .ARM.exidx :
+ {
+ /* (C++) Index entries for section unwinding */
+ *(.ARM.exidx* .gnu.linkonce.armexidx.*)
+ } > FLASH
+ PROVIDE_HIDDEN (__exidx_end = .);
+
+ /DISCARD/ :
+ {
+ *(.eh_frame)
+ }
+}
+
+ASSERT((_stack_top_aligned - _stack_top_unaligned) == 0, "
+STACK_SIZE must be 8 byte multiple")
diff --git a/core/runtime/layouts/apollo3.ld b/core/runtime/layouts/apollo3.ld
new file mode 100644
index 0000000..8713c27
--- /dev/null
+++ b/core/runtime/layouts/apollo3.ld
@@ -0,0 +1,11 @@
+/* Layout for the Apollo3 MCU, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x00040040, LENGTH = 0x0005FFC0
+ SRAM (rwx) : ORIGIN = 0x10002000, LENGTH = 0x2000
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/hail.ld b/core/runtime/layouts/hail.ld
new file mode 100644
index 0000000..179a883
--- /dev/null
+++ b/core/runtime/layouts/hail.ld
@@ -0,0 +1,11 @@
+/* Layout for the Hail board, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x0005FFC0
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/hifive1.ld b/core/runtime/layouts/hifive1.ld
new file mode 100644
index 0000000..20294ce
--- /dev/null
+++ b/core/runtime/layouts/hifive1.ld
@@ -0,0 +1,18 @@
+/* Layout for the RISC-V 32 boards, used by the examples in this repository. */
+
+MEMORY {
+ /*
+ * The TBF header can change in size so use 0x40 combined with
+ * --protected-region-size with elf2tab to cover a header upto that
+ * size.
+ *
+ * Note that the SRAM address may need to be changed depending on
+ * the kernel binary, check for the actual address of APP_MEMORY!
+ */
+ FLASH (rx) : ORIGIN = 0x20040040, LENGTH = 32M
+ SRAM (rwx) : ORIGIN = 0x80002400, LENGTH = 0x1C00
+}
+
+MPU_MIN_ALIGN = 1K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/imxrt1050.ld b/core/runtime/layouts/imxrt1050.ld
new file mode 100644
index 0000000..3e7f904
--- /dev/null
+++ b/core/runtime/layouts/imxrt1050.ld
@@ -0,0 +1,11 @@
+/* Layout for the iMX.RT1050 board, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x63002040, LENGTH = 0xFFFFC0
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 112K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/msp432.ld b/core/runtime/layouts/msp432.ld
new file mode 100644
index 0000000..2c9d2a4
--- /dev/null
+++ b/core/runtime/layouts/msp432.ld
@@ -0,0 +1,9 @@
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x00020040, LENGTH = 0x0001FFC0
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 0x2000
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/nrf52.ld b/core/runtime/layouts/nrf52.ld
new file mode 100644
index 0000000..942e86b
--- /dev/null
+++ b/core/runtime/layouts/nrf52.ld
@@ -0,0 +1,11 @@
+/* Layout for the nRF52-DK, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x0005FFC0
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/nrf52840.ld b/core/runtime/layouts/nrf52840.ld
new file mode 100644
index 0000000..31bf346
--- /dev/null
+++ b/core/runtime/layouts/nrf52840.ld
@@ -0,0 +1,11 @@
+/* Layout for the nRF52840-DK, usable by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x00030040, LENGTH = 0x000CFFC0
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 62K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/nucleo_f429zi.ld b/core/runtime/layouts/nucleo_f429zi.ld
new file mode 100644
index 0000000..3e407b4
--- /dev/null
+++ b/core/runtime/layouts/nucleo_f429zi.ld
@@ -0,0 +1,11 @@
+/* Layout for the Nucleo F429zi, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x08040040, LENGTH = 255K
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 112K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/nucleo_f446re.ld b/core/runtime/layouts/nucleo_f446re.ld
new file mode 100644
index 0000000..83e698b
--- /dev/null
+++ b/core/runtime/layouts/nucleo_f446re.ld
@@ -0,0 +1,11 @@
+/* Layout for the Nucleo F446re, used by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x08040040, LENGTH = 255K
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 176K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/opentitan.ld b/core/runtime/layouts/opentitan.ld
new file mode 100644
index 0000000..ada781c
--- /dev/null
+++ b/core/runtime/layouts/opentitan.ld
@@ -0,0 +1,18 @@
+/* Layout for the RISC-V 32 boards, used by the examples in this repository. */
+
+MEMORY {
+ /*
+ * The TBF header can change in size so use 0x40 combined with
+ * --protected-region-size with elf2tab to cover a header upto that
+ * size.
+ *
+ * Note that the SRAM address may need to be changed depending on
+ * the kernel binary, check for the actual address of APP_MEMORY!
+ */
+ FLASH (rx) : ORIGIN = 0x20030040, LENGTH = 32M
+ SRAM (rwx) : ORIGIN = 0x10004000, LENGTH = 512K
+}
+
+MPU_MIN_ALIGN = 1K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/layouts/stm32f3discovery.ld b/core/runtime/layouts/stm32f3discovery.ld
new file mode 100644
index 0000000..9368003
--- /dev/null
+++ b/core/runtime/layouts/stm32f3discovery.ld
@@ -0,0 +1,11 @@
+/* Layout for the stm32f3discovery board, usable by the examples in this repository. */
+
+MEMORY {
+ /* The application region is 64 bytes (0x40) */
+ FLASH (rx) : ORIGIN = 0x08020040, LENGTH = 0x00020000
+ SRAM (rwx) : ORIGIN = 0x20004000, LENGTH = 48K
+}
+
+MPU_MIN_ALIGN = 8K;
+
+INCLUDE layout_generic.ld
diff --git a/core/runtime/src/lib.rs b/core/runtime/src/lib.rs
new file mode 100644
index 0000000..d7ea623
--- /dev/null
+++ b/core/runtime/src/lib.rs
@@ -0,0 +1,21 @@
+//! `libtock_runtime` provides the runtime for Tock process binaries written in
+//! Rust as well as interfaces to Tock's system calls.
+//!
+//! `libtock_runtime` is designed for statically-compiled binaries, and needs to
+//! know the location (in non-volatile memory and RAM) at which the process will
+//! execute. It reads the `LIBTOCK_PLATFORM` variable to determine what location
+//! to build for (see the `layouts/` directory to see what platforms are
+//! available). It expects the following cargo config options to be set (e.g. in
+//! `.cargo/config`):
+//! ```
+//! [build]
+//! rustflags = [
+//! "-C", "relocation-model=static",
+//! "-C", "link-arg=-Tlayout.ld",
+//! ]
+//! ```
+//! If a process binary wants to support another platform, it can set the
+//! `no_auto_layout` feature on `libtock_runtime` to disable this functionality
+//! and provide its own layout file.
+
+#![no_std]