// Program to generate core files to test MTE tag features.
//
// This file uses ACLE intrinsics as detailed in:
// https://developer.arm.com/documentation/101028/0012/10--Memory-tagging-intrinsics?lang=en
//
// Compile with:
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -o a.out.mte
// (use a.out.mte to generate core.mte.notags and core.mte)
// <gcc or clang> -march=armv8.5-a+memtag -g main.c -DNO_MTE -o a.out.nomte
//
// Set /proc/self/coredump_filter to the following values when generating the
// core files:
// * core.mte - 3
// * core.mte.notags - 2
// * core.nomte - 3

#include <arm_acle.h>
#include <asm/mman.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>

int main(int argc, char const *argv[]) {
#ifdef NO_MTE
  __builtin_trap();
#endif

  if (prctl(PR_SET_TAGGED_ADDR_CTRL,
            PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
                // Allow all tags to be generated by the addg
                // instruction __arm_mte_increment_tag produces.
                (0xffff << PR_MTE_TAG_SHIFT),
            0, 0, 0)) {
    return 1;
  }

  size_t page_size = sysconf(_SC_PAGESIZE);
  char *mte_buf = mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_MTE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (!mte_buf)
    return 1;

  printf("mte_buf: %p\n", mte_buf);

  // Allocate some untagged memory before the tagged memory.
  char *buf = mmap(0, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (!buf)
    return 1;

  printf("buf: %p\n", buf);

  // This write means that the memory for buf is included in the corefile.
  // So we can read from the end of it into mte_buf during the test.
  *buf = 1;

  // These must be next to each other for the tests to work.
  // <high address>
  // mte_buf
  // buf
  // <low address>
  if ((mte_buf - buf) != page_size) {
    return 1;
  }

  // Set incrementing tags until end of the page.
  char *tagged_ptr = mte_buf;
  // This ignores tag bits when subtracting the addresses.
  while (__arm_mte_ptrdiff(tagged_ptr, mte_buf) < page_size) {
    // Set the allocation tag for this location.
    __arm_mte_set_tag(tagged_ptr);
    // + 16 for 16 byte granules.
    // Earlier we allowed all tag values, so this will give us an
    // incrementing pattern 0-0xF wrapping back to 0.
    tagged_ptr = __arm_mte_increment_tag(tagged_ptr + 16, 1);
  }

  // Will fault because logical tag 0 != allocation tag 1.
  *(mte_buf + 16) = 1;

  return 0;
}
