summaryrefslogtreecommitdiffstats
path: root/src/tests/vm
diff options
context:
space:
mode:
authorFelipe Boeira <felipe.boeira@liu.se>2019-01-08 18:39:03 +0100
committerFelipe Boeira <felipe.boeira@liu.se>2019-01-08 18:39:03 +0100
commitd4522b8e9854178473adcea0fbb84f23f6e744bd (patch)
treefbcf620617c5023154eba3f965b3a982daa64a47 /src/tests/vm
downloadpintos-d4522b8e9854178473adcea0fbb84f23f6e744bd.tar.gz
Initial commit
Diffstat (limited to 'src/tests/vm')
-rw-r--r--src/tests/vm/Grading12
-rw-r--r--src/tests/vm/Make.tests103
-rw-r--r--src/tests/vm/Rubric.functionality30
-rw-r--r--src/tests/vm/Rubric.robustness21
-rw-r--r--src/tests/vm/child-inherit.c16
-rw-r--r--src/tests/vm/child-linear.c36
-rw-r--r--src/tests/vm/child-mm-wrt.c24
-rw-r--r--src/tests/vm/child-qsort-mm.c25
-rw-r--r--src/tests/vm/child-qsort.c32
-rw-r--r--src/tests/vm/child-sort.c42
-rw-r--r--src/tests/vm/mmap-bad-fd.c15
-rw-r--r--src/tests/vm/mmap-bad-fd.ck15
-rw-r--r--src/tests/vm/mmap-clean.c53
-rw-r--r--src/tests/vm/mmap-clean.ck16
-rw-r--r--src/tests/vm/mmap-close.c27
-rw-r--r--src/tests/vm/mmap-close.ck11
-rw-r--r--src/tests/vm/mmap-exit.c22
-rw-r--r--src/tests/vm/mmap-exit.ck17
-rw-r--r--src/tests/vm/mmap-inherit.c32
-rw-r--r--src/tests/vm/mmap-inherit.ck16
-rw-r--r--src/tests/vm/mmap-misalign.c16
-rw-r--r--src/tests/vm/mmap-misalign.ck11
-rw-r--r--src/tests/vm/mmap-null.c15
-rw-r--r--src/tests/vm/mmap-null.ck11
-rw-r--r--src/tests/vm/mmap-over-code.c19
-rw-r--r--src/tests/vm/mmap-over-code.ck11
-rw-r--r--src/tests/vm/mmap-over-data.c21
-rw-r--r--src/tests/vm/mmap-over-data.ck11
-rw-r--r--src/tests/vm/mmap-over-stk.c19
-rw-r--r--src/tests/vm/mmap-over-stk.ck11
-rw-r--r--src/tests/vm/mmap-overlap.c20
-rw-r--r--src/tests/vm/mmap-overlap.ck13
-rw-r--r--src/tests/vm/mmap-read.c32
-rw-r--r--src/tests/vm/mmap-read.ck11
-rw-r--r--src/tests/vm/mmap-remove.c43
-rw-r--r--src/tests/vm/mmap-remove.ck14
-rw-r--r--src/tests/vm/mmap-shuffle.c38
-rw-r--r--src/tests/vm/mmap-shuffle.ck47
-rw-r--r--src/tests/vm/mmap-twice.c28
-rw-r--r--src/tests/vm/mmap-twice.ck15
-rw-r--r--src/tests/vm/mmap-unmap.c23
-rw-r--r--src/tests/vm/mmap-unmap.ck7
-rw-r--r--src/tests/vm/mmap-write.c32
-rw-r--r--src/tests/vm/mmap-write.ck13
-rw-r--r--src/tests/vm/mmap-zero.c27
-rw-r--r--src/tests/vm/mmap-zero.ck12
-rw-r--r--src/tests/vm/page-linear.c44
-rw-r--r--src/tests/vm/page-linear.ck14
-rw-r--r--src/tests/vm/page-merge-mm.c8
-rw-r--r--src/tests/vm/page-merge-mm.ck29
-rw-r--r--src/tests/vm/page-merge-par.c8
-rw-r--r--src/tests/vm/page-merge-par.ck29
-rw-r--r--src/tests/vm/page-merge-seq.c137
-rw-r--r--src/tests/vm/page-merge-seq.ck29
-rw-r--r--src/tests/vm/page-merge-stk.c8
-rw-r--r--src/tests/vm/page-merge-stk.ck29
-rw-r--r--src/tests/vm/page-parallel.c21
-rw-r--r--src/tests/vm/page-parallel.ck17
-rw-r--r--src/tests/vm/page-shuffle.c30
-rw-r--r--src/tests/vm/page-shuffle.ck44
-rw-r--r--src/tests/vm/parallel-merge.c149
-rw-r--r--src/tests/vm/parallel-merge.h6
-rw-r--r--src/tests/vm/process_death.pm22
-rw-r--r--src/tests/vm/pt-bad-addr.c11
-rw-r--r--src/tests/vm/pt-bad-addr.ck7
-rw-r--r--src/tests/vm/pt-bad-read.c16
-rw-r--r--src/tests/vm/pt-bad-read.ck10
-rw-r--r--src/tests/vm/pt-big-stk-obj.c20
-rw-r--r--src/tests/vm/pt-big-stk-obj.ck10
-rw-r--r--src/tests/vm/pt-grow-bad.c14
-rw-r--r--src/tests/vm/pt-grow-bad.ck9
-rw-r--r--src/tests/vm/pt-grow-pusha.c20
-rw-r--r--src/tests/vm/pt-grow-pusha.ck9
-rw-r--r--src/tests/vm/pt-grow-stack.c20
-rw-r--r--src/tests/vm/pt-grow-stack.ck10
-rw-r--r--src/tests/vm/pt-grow-stk-sc.c32
-rw-r--r--src/tests/vm/pt-grow-stk-sc.ck15
-rw-r--r--src/tests/vm/pt-write-code-2.c15
-rw-r--r--src/tests/vm/pt-write-code.c12
-rw-r--r--src/tests/vm/pt-write-code.ck7
-rw-r--r--src/tests/vm/pt-write-code2.ck10
-rw-r--r--src/tests/vm/qsort.c136
-rw-r--r--src/tests/vm/qsort.h8
-rw-r--r--src/tests/vm/sample.inc19
-rw-r--r--src/tests/vm/sample.txt17
85 files changed, 2136 insertions, 0 deletions
diff --git a/src/tests/vm/Grading b/src/tests/vm/Grading
new file mode 100644
index 0000000..f0c2c13
--- /dev/null
+++ b/src/tests/vm/Grading
@@ -0,0 +1,12 @@
+# Percentage of the testing point total designated for each set of
+# tests.
+
+# This project is primarily about virtual memory, but all the previous
+# functionality should work too, and it's easy to screw it up, thus
+# the equal weight placed on each.
+
+50% tests/vm/Rubric.functionality
+15% tests/vm/Rubric.robustness
+10% tests/userprog/Rubric.functionality
+5% tests/userprog/Rubric.robustness
+20% tests/filesys/base/Rubric
diff --git a/src/tests/vm/Make.tests b/src/tests/vm/Make.tests
new file mode 100644
index 0000000..04b1b81
--- /dev/null
+++ b/src/tests/vm/Make.tests
@@ -0,0 +1,103 @@
+# -*- makefile -*-
+
+tests/vm_TESTS = $(addprefix tests/vm/,pt-grow-stack pt-grow-pusha \
+pt-grow-bad pt-big-stk-obj pt-bad-addr pt-bad-read pt-write-code \
+pt-write-code2 pt-grow-stk-sc page-linear page-parallel page-merge-seq \
+page-merge-par page-merge-stk page-merge-mm page-shuffle mmap-read \
+mmap-close mmap-unmap mmap-overlap mmap-twice mmap-write mmap-exit \
+mmap-shuffle mmap-bad-fd mmap-clean mmap-inherit mmap-misalign \
+mmap-null mmap-over-code mmap-over-data mmap-over-stk mmap-remove \
+mmap-zero)
+
+tests/vm_PROGS = $(tests/vm_TESTS) $(addprefix tests/vm/,child-linear \
+child-sort child-qsort child-qsort-mm child-mm-wrt child-inherit)
+
+tests/vm/pt-grow-stack_SRC = tests/vm/pt-grow-stack.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-grow-pusha_SRC = tests/vm/pt-grow-pusha.c tests/lib.c \
+tests/main.c
+tests/vm/pt-grow-bad_SRC = tests/vm/pt-grow-bad.c tests/lib.c tests/main.c
+tests/vm/pt-big-stk-obj_SRC = tests/vm/pt-big-stk-obj.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/pt-bad-addr_SRC = tests/vm/pt-bad-addr.c tests/lib.c tests/main.c
+tests/vm/pt-bad-read_SRC = tests/vm/pt-bad-read.c tests/lib.c tests/main.c
+tests/vm/pt-write-code_SRC = tests/vm/pt-write-code.c tests/lib.c tests/main.c
+tests/vm/pt-write-code2_SRC = tests/vm/pt-write-code-2.c tests/lib.c tests/main.c
+tests/vm/pt-grow-stk-sc_SRC = tests/vm/pt-grow-stk-sc.c tests/lib.c tests/main.c
+tests/vm/page-linear_SRC = tests/vm/page-linear.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-parallel_SRC = tests/vm/page-parallel.c tests/lib.c tests/main.c
+tests/vm/page-merge-seq_SRC = tests/vm/page-merge-seq.c tests/arc4.c \
+tests/lib.c tests/main.c
+tests/vm/page-merge-par_SRC = tests/vm/page-merge-par.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-stk_SRC = tests/vm/page-merge-stk.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-merge-mm_SRC = tests/vm/page-merge-mm.c \
+tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c
+tests/vm/page-shuffle_SRC = tests/vm/page-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-read_SRC = tests/vm/mmap-read.c tests/lib.c tests/main.c
+tests/vm/mmap-close_SRC = tests/vm/mmap-close.c tests/lib.c tests/main.c
+tests/vm/mmap-unmap_SRC = tests/vm/mmap-unmap.c tests/lib.c tests/main.c
+tests/vm/mmap-overlap_SRC = tests/vm/mmap-overlap.c tests/lib.c tests/main.c
+tests/vm/mmap-twice_SRC = tests/vm/mmap-twice.c tests/lib.c tests/main.c
+tests/vm/mmap-write_SRC = tests/vm/mmap-write.c tests/lib.c tests/main.c
+tests/vm/mmap-exit_SRC = tests/vm/mmap-exit.c tests/lib.c tests/main.c
+tests/vm/mmap-shuffle_SRC = tests/vm/mmap-shuffle.c tests/arc4.c \
+tests/cksum.c tests/lib.c tests/main.c
+tests/vm/mmap-bad-fd_SRC = tests/vm/mmap-bad-fd.c tests/lib.c tests/main.c
+tests/vm/mmap-clean_SRC = tests/vm/mmap-clean.c tests/lib.c tests/main.c
+tests/vm/mmap-inherit_SRC = tests/vm/mmap-inherit.c tests/lib.c tests/main.c
+tests/vm/mmap-misalign_SRC = tests/vm/mmap-misalign.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-null_SRC = tests/vm/mmap-null.c tests/lib.c tests/main.c
+tests/vm/mmap-over-code_SRC = tests/vm/mmap-over-code.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-data_SRC = tests/vm/mmap-over-data.c tests/lib.c \
+tests/main.c
+tests/vm/mmap-over-stk_SRC = tests/vm/mmap-over-stk.c tests/lib.c tests/main.c
+tests/vm/mmap-remove_SRC = tests/vm/mmap-remove.c tests/lib.c tests/main.c
+tests/vm/mmap-zero_SRC = tests/vm/mmap-zero.c tests/lib.c tests/main.c
+
+tests/vm/child-linear_SRC = tests/vm/child-linear.c tests/arc4.c tests/lib.c
+tests/vm/child-qsort_SRC = tests/vm/child-qsort.c tests/vm/qsort.c tests/lib.c
+tests/vm/child-qsort-mm_SRC = tests/vm/child-qsort-mm.c tests/vm/qsort.c \
+tests/lib.c
+tests/vm/child-sort_SRC = tests/vm/child-sort.c tests/lib.c
+tests/vm/child-mm-wrt_SRC = tests/vm/child-mm-wrt.c tests/lib.c tests/main.c
+tests/vm/child-inherit_SRC = tests/vm/child-inherit.c tests/lib.c tests/main.c
+
+tests/vm/pt-bad-read_PUTFILES = tests/vm/sample.txt
+tests/vm/pt-write-code2_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-close_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-read_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-unmap_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-twice_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-overlap_PUTFILES = tests/vm/zeros
+tests/vm/mmap-exit_PUTFILES = tests/vm/child-mm-wrt
+tests/vm/page-parallel_PUTFILES = tests/vm/child-linear
+tests/vm/page-merge-seq_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-par_PUTFILES = tests/vm/child-sort
+tests/vm/page-merge-stk_PUTFILES = tests/vm/child-qsort
+tests/vm/page-merge-mm_PUTFILES = tests/vm/child-qsort-mm
+tests/vm/mmap-clean_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-inherit_PUTFILES = tests/vm/sample.txt tests/vm/child-inherit
+tests/vm/mmap-misalign_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-null_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-code_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-data_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-over-stk_PUTFILES = tests/vm/sample.txt
+tests/vm/mmap-remove_PUTFILES = tests/vm/sample.txt
+
+tests/vm/page-linear.output: TIMEOUT = 300
+tests/vm/page-shuffle.output: TIMEOUT = 600
+tests/vm/mmap-shuffle.output: TIMEOUT = 600
+tests/vm/page-merge-seq.output: TIMEOUT = 600
+tests/vm/page-merge-par.output: TIMEOUT = 600
+
+tests/vm/zeros:
+ dd if=/dev/zero of=$@ bs=1024 count=6
+
+clean::
+ rm -f tests/vm/zeros
diff --git a/src/tests/vm/Rubric.functionality b/src/tests/vm/Rubric.functionality
new file mode 100644
index 0000000..8a86612
--- /dev/null
+++ b/src/tests/vm/Rubric.functionality
@@ -0,0 +1,30 @@
+Functionality of virtual memory subsystem:
+- Test stack growth.
+3 pt-grow-stack
+3 pt-grow-stk-sc
+3 pt-big-stk-obj
+3 pt-grow-pusha
+
+- Test paging behavior.
+3 page-linear
+3 page-parallel
+3 page-shuffle
+4 page-merge-seq
+4 page-merge-par
+4 page-merge-mm
+4 page-merge-stk
+
+- Test "mmap" system call.
+2 mmap-read
+2 mmap-write
+2 mmap-shuffle
+
+2 mmap-twice
+
+2 mmap-unmap
+1 mmap-exit
+
+3 mmap-clean
+
+2 mmap-close
+2 mmap-remove
diff --git a/src/tests/vm/Rubric.robustness b/src/tests/vm/Rubric.robustness
new file mode 100644
index 0000000..0b2552f
--- /dev/null
+++ b/src/tests/vm/Rubric.robustness
@@ -0,0 +1,21 @@
+Robustness of virtual memory subsystem:
+- Test robustness of page table support.
+2 pt-bad-addr
+3 pt-bad-read
+2 pt-write-code
+3 pt-write-code2
+4 pt-grow-bad
+
+- Test robustness of "mmap" system call.
+1 mmap-bad-fd
+1 mmap-inherit
+1 mmap-null
+1 mmap-zero
+
+2 mmap-misalign
+
+2 mmap-over-code
+2 mmap-over-data
+2 mmap-over-stk
+2 mmap-overlap
+
diff --git a/src/tests/vm/child-inherit.c b/src/tests/vm/child-inherit.c
new file mode 100644
index 0000000..d3186a1
--- /dev/null
+++ b/src/tests/vm/child-inherit.c
@@ -0,0 +1,16 @@
+/* Child process for mmap-inherit test.
+ Tries to write to a mapping present in the parent.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ memset ((char *) 0x54321000, 0, 4096);
+ fail ("child can modify parent's memory mappings");
+}
+
diff --git a/src/tests/vm/child-linear.c b/src/tests/vm/child-linear.c
new file mode 100644
index 0000000..eca3e3f
--- /dev/null
+++ b/src/tests/vm/child-linear.c
@@ -0,0 +1,36 @@
+/* Child process of page-parallel.
+ Encrypts 1 MB of zeros, then decrypts it, and ensures that
+ the zeros are back. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+const char *test_name = "child-linear";
+
+#define SIZE (1024 * 1024)
+static char buf[SIZE];
+
+int
+main (int argc, char *argv[])
+{
+ const char *key = argv[argc - 1];
+ struct arc4 arc4;
+ size_t i;
+
+ /* Encrypt zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ arc4_init (&arc4, key, strlen (key));
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all zeros. */
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != '\0')
+ fail ("byte %zu != 0", i);
+
+ return 0x42;
+}
diff --git a/src/tests/vm/child-mm-wrt.c b/src/tests/vm/child-mm-wrt.c
new file mode 100644
index 0000000..8419788
--- /dev/null
+++ b/src/tests/vm/child-mm-wrt.c
@@ -0,0 +1,24 @@
+/* Child process of mmap-exit.
+ Mmaps a file and writes to it via the mmap'ing, then exits
+ without calling munmap. The data in the mapped region must be
+ written out at program termination. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK (create ("sample.txt", sizeof sample), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, ACTUAL) != MAP_FAILED, "mmap \"sample.txt\"");
+ memcpy (ACTUAL, sample, sizeof sample);
+}
+
diff --git a/src/tests/vm/child-qsort-mm.c b/src/tests/vm/child-qsort-mm.c
new file mode 100644
index 0000000..db45499
--- /dev/null
+++ b/src/tests/vm/child-qsort-mm.c
@@ -0,0 +1,25 @@
+/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort,
+ a multi-pass divide and conquer algorithm. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort-mm";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char *p = (unsigned char *) 0x10000000;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+ CHECK (mmap (handle, p) != MAP_FAILED, "mmap \"%s\"", argv[1]);
+ qsort_bytes (p, 1024 * 128);
+
+ return 80;
+}
diff --git a/src/tests/vm/child-qsort.c b/src/tests/vm/child-qsort.c
new file mode 100644
index 0000000..355f4eb
--- /dev/null
+++ b/src/tests/vm/child-qsort.c
@@ -0,0 +1,32 @@
+/* Reads a 128 kB file onto the stack and "sorts" the bytes in
+ it, using quick sort, a multi-pass divide and conquer
+ algorithm. The sorted data is written back to the same file
+ in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+#include "tests/vm/qsort.h"
+
+const char *test_name = "child-qsort";
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char buf[128 * 1024];
+ size_t size;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+
+ size = read (handle, buf, sizeof buf);
+ qsort_bytes (buf, sizeof buf);
+ seek (handle, 0);
+ write (handle, buf, size);
+ close (handle);
+
+ return 72;
+}
diff --git a/src/tests/vm/child-sort.c b/src/tests/vm/child-sort.c
new file mode 100644
index 0000000..dff2c77
--- /dev/null
+++ b/src/tests/vm/child-sort.c
@@ -0,0 +1,42 @@
+/* Reads a 128 kB file into static data and "sorts" the bytes in
+ it, using counting sort, a single-pass algorithm. The sorted
+ data is written back to the same file in-place. */
+
+#include <debug.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+const char *test_name = "child-sort";
+
+unsigned char buf[128 * 1024];
+size_t histogram[256];
+
+int
+main (int argc UNUSED, char *argv[])
+{
+ int handle;
+ unsigned char *p;
+ size_t size;
+ size_t i;
+
+ quiet = true;
+
+ CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]);
+
+ size = read (handle, buf, sizeof buf);
+ for (i = 0; i < size; i++)
+ histogram[buf[i]]++;
+ p = buf;
+ for (i = 0; i < sizeof histogram / sizeof *histogram; i++)
+ {
+ size_t j = histogram[i];
+ while (j-- > 0)
+ *p++ = i;
+ }
+ seek (handle, 0);
+ write (handle, buf, size);
+ close (handle);
+
+ return 123;
+}
diff --git a/src/tests/vm/mmap-bad-fd.c b/src/tests/vm/mmap-bad-fd.c
new file mode 100644
index 0000000..76a7b50
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.c
@@ -0,0 +1,15 @@
+/* Tries to mmap an invalid fd,
+ which must either fail silently or terminate the process with
+ exit code -1. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ CHECK (mmap (0x5678, (void *) 0x10000000) == MAP_FAILED,
+ "try to mmap invalid fd");
+}
+
diff --git a/src/tests/vm/mmap-bad-fd.ck b/src/tests/vm/mmap-bad-fd.ck
new file mode 100644
index 0000000..f3f58d5
--- /dev/null
+++ b/src/tests/vm/mmap-bad-fd.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF', <<'EOF']);
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+(mmap-bad-fd) end
+mmap-bad-fd: exit(0)
+EOF
+(mmap-bad-fd) begin
+(mmap-bad-fd) try to mmap invalid fd
+mmap-bad-fd: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/mmap-clean.c b/src/tests/vm/mmap-clean.c
new file mode 100644
index 0000000..ea1dc9c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.c
@@ -0,0 +1,53 @@
+/* Verifies that mmap'd regions are only written back on munmap
+ if the data was actually modified in memory. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ static const char overwrite[] = "Now is the time for all good...";
+ static char buffer[sizeof sample - 1];
+ char *actual = (char *) 0x54321000;
+ int handle;
+ mapid_t map;
+
+ /* Open file, map, verify data. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Modify file. */
+ CHECK (write (handle, overwrite, strlen (overwrite))
+ == (int) strlen (overwrite),
+ "write \"sample.txt\"");
+
+ /* Close mapping. Data should not be written back, because we
+ didn't modify it via the mapping. */
+ msg ("munmap \"sample.txt\"");
+ munmap (map);
+
+ /* Read file back. */
+ msg ("seek \"sample.txt\"");
+ seek (handle, 0);
+ CHECK (read (handle, buffer, sizeof buffer) == sizeof buffer,
+ "read \"sample.txt\"");
+
+ /* Verify that file overwrite worked. */
+ if (memcmp (buffer, overwrite, strlen (overwrite))
+ || memcmp (buffer + strlen (overwrite), sample + strlen (overwrite),
+ strlen (sample) - strlen (overwrite)))
+ {
+ if (!memcmp (buffer, sample, strlen (sample)))
+ fail ("munmap wrote back clean page");
+ else
+ fail ("read surprising data from file");
+ }
+ else
+ msg ("file change was retained after munmap");
+}
diff --git a/src/tests/vm/mmap-clean.ck b/src/tests/vm/mmap-clean.ck
new file mode 100644
index 0000000..1666d6c
--- /dev/null
+++ b/src/tests/vm/mmap-clean.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-clean) begin
+(mmap-clean) open "sample.txt"
+(mmap-clean) mmap "sample.txt"
+(mmap-clean) write "sample.txt"
+(mmap-clean) munmap "sample.txt"
+(mmap-clean) seek "sample.txt"
+(mmap-clean) read "sample.txt"
+(mmap-clean) file change was retained after munmap
+(mmap-clean) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-close.c b/src/tests/vm/mmap-close.c
new file mode 100644
index 0000000..d016ee3
--- /dev/null
+++ b/src/tests/vm/mmap-close.c
@@ -0,0 +1,27 @@
+/* Verifies that memory mappings persist after file close. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ close (handle);
+
+ if (memcmp (ACTUAL, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ munmap (map);
+}
diff --git a/src/tests/vm/mmap-close.ck b/src/tests/vm/mmap-close.ck
new file mode 100644
index 0000000..d15e41a
--- /dev/null
+++ b/src/tests/vm/mmap-close.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-close) begin
+(mmap-close) open "sample.txt"
+(mmap-close) mmap "sample.txt"
+(mmap-close) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-exit.c b/src/tests/vm/mmap-exit.c
new file mode 100644
index 0000000..7a2278a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.c
@@ -0,0 +1,22 @@
+/* Executes child-mm-wrt and verifies that the writes that should
+ have occurred really did. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ pid_t child;
+
+ /* Make child write file. */
+ quiet = true;
+ CHECK ((child = exec ("child-mm-wrt")) != -1, "exec \"child-mm-wrt\"");
+ CHECK (wait (child) == 0, "wait for child (should return 0)");
+ quiet = false;
+
+ /* Check file contents. */
+ check_file ("sample.txt", sample, sizeof sample);
+}
diff --git a/src/tests/vm/mmap-exit.ck b/src/tests/vm/mmap-exit.ck
new file mode 100644
index 0000000..457d34a
--- /dev/null
+++ b/src/tests/vm/mmap-exit.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-exit) begin
+(child-mm-wrt) begin
+(child-mm-wrt) create "sample.txt"
+(child-mm-wrt) open "sample.txt"
+(child-mm-wrt) mmap "sample.txt"
+(child-mm-wrt) end
+(mmap-exit) open "sample.txt" for verification
+(mmap-exit) verified contents of "sample.txt"
+(mmap-exit) close "sample.txt"
+(mmap-exit) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-inherit.c b/src/tests/vm/mmap-inherit.c
new file mode 100644
index 0000000..7fa9607
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.c
@@ -0,0 +1,32 @@
+/* Maps a file into memory and runs child-inherit to verify that
+ mappings are not inherited. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x54321000;
+ int handle;
+ pid_t child;
+
+ /* Open file, map, verify data. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, actual) != MAP_FAILED, "mmap \"sample.txt\"");
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Spawn child and wait. */
+ CHECK ((child = exec ("child-inherit")) != -1, "exec \"child-inherit\"");
+ quiet = true;
+ CHECK (wait (child) == -1, "wait for child (should return -1)");
+ quiet = false;
+
+ /* Verify data again. */
+ CHECK (!memcmp (actual, sample, strlen (sample)),
+ "checking that mmap'd file still has same data");
+}
diff --git a/src/tests/vm/mmap-inherit.ck b/src/tests/vm/mmap-inherit.ck
new file mode 100644
index 0000000..7e69122
--- /dev/null
+++ b/src/tests/vm/mmap-inherit.ck
@@ -0,0 +1,16 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ( [<<'EOF']);
+(mmap-inherit) begin
+(mmap-inherit) open "sample.txt"
+(mmap-inherit) mmap "sample.txt"
+(mmap-inherit) exec "child-inherit"
+(child-inherit) begin
+child-inherit: exit(-1)
+(mmap-inherit) checking that mmap'd file still has same data
+(mmap-inherit) end
+mmap-inherit: exit(0)
+EOF
+pass;
diff --git a/src/tests/vm/mmap-misalign.c b/src/tests/vm/mmap-misalign.c
new file mode 100644
index 0000000..34141a9
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.c
@@ -0,0 +1,16 @@
+/* Verifies that misaligned memory mappings are disallowed. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) 0x10001234) == MAP_FAILED,
+ "try to mmap at misaligned address");
+}
+
diff --git a/src/tests/vm/mmap-misalign.ck b/src/tests/vm/mmap-misalign.ck
new file mode 100644
index 0000000..145a2e8
--- /dev/null
+++ b/src/tests/vm/mmap-misalign.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-misalign) begin
+(mmap-misalign) open "sample.txt"
+(mmap-misalign) try to mmap at misaligned address
+(mmap-misalign) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-null.c b/src/tests/vm/mmap-null.c
new file mode 100644
index 0000000..f8ef075
--- /dev/null
+++ b/src/tests/vm/mmap-null.c
@@ -0,0 +1,15 @@
+/* Verifies that memory mappings at address 0 are disallowed. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, NULL) == MAP_FAILED, "try to mmap at address 0");
+}
+
diff --git a/src/tests/vm/mmap-null.ck b/src/tests/vm/mmap-null.ck
new file mode 100644
index 0000000..aacdd65
--- /dev/null
+++ b/src/tests/vm/mmap-null.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-null) begin
+(mmap-null) open "sample.txt"
+(mmap-null) try to mmap at address 0
+(mmap-null) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-code.c b/src/tests/vm/mmap-over-code.c
new file mode 100644
index 0000000..d3619a3
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the code segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ uintptr_t test_main_page = ROUND_DOWN ((uintptr_t) test_main, 4096);
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) test_main_page) == MAP_FAILED,
+ "try to mmap over code segment");
+}
+
diff --git a/src/tests/vm/mmap-over-code.ck b/src/tests/vm/mmap-over-code.ck
new file mode 100644
index 0000000..b5b23c7
--- /dev/null
+++ b/src/tests/vm/mmap-over-code.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-code) begin
+(mmap-over-code) open "sample.txt"
+(mmap-over-code) try to mmap over code segment
+(mmap-over-code) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-data.c b/src/tests/vm/mmap-over-data.c
new file mode 100644
index 0000000..9ea5d49
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.c
@@ -0,0 +1,21 @@
+/* Verifies that mapping over the data segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+static char x;
+
+void
+test_main (void)
+{
+ uintptr_t x_page = ROUND_DOWN ((uintptr_t) &x, 4096);
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) x_page) == MAP_FAILED,
+ "try to mmap over data segment");
+}
+
diff --git a/src/tests/vm/mmap-over-data.ck b/src/tests/vm/mmap-over-data.ck
new file mode 100644
index 0000000..98770cc
--- /dev/null
+++ b/src/tests/vm/mmap-over-data.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-data) begin
+(mmap-over-data) open "sample.txt"
+(mmap-over-data) try to mmap over data segment
+(mmap-over-data) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-over-stk.c b/src/tests/vm/mmap-over-stk.c
new file mode 100644
index 0000000..4e241e8
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.c
@@ -0,0 +1,19 @@
+/* Verifies that mapping over the stack segment is disallowed. */
+
+#include <stdint.h>
+#include <round.h>
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ uintptr_t handle_page = ROUND_DOWN ((uintptr_t) &handle, 4096);
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (mmap (handle, (void *) handle_page) == MAP_FAILED,
+ "try to mmap over stack segment");
+}
+
diff --git a/src/tests/vm/mmap-over-stk.ck b/src/tests/vm/mmap-over-stk.ck
new file mode 100644
index 0000000..e6880cf
--- /dev/null
+++ b/src/tests/vm/mmap-over-stk.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-over-stk) begin
+(mmap-over-stk) open "sample.txt"
+(mmap-over-stk) try to mmap over stack segment
+(mmap-over-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-overlap.c b/src/tests/vm/mmap-overlap.c
new file mode 100644
index 0000000..668ae5f
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.c
@@ -0,0 +1,20 @@
+/* Verifies that overlapping memory mappings are disallowed. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *start = (char *) 0x10000000;
+ int fd[2];
+
+ CHECK ((fd[0] = open ("zeros")) > 1, "open \"zeros\" once");
+ CHECK (mmap (fd[0], start) != MAP_FAILED, "mmap \"zeros\"");
+ CHECK ((fd[1] = open ("zeros")) > 1 && fd[0] != fd[1],
+ "open \"zeros\" again");
+ CHECK (mmap (fd[1], start + 4096) == MAP_FAILED,
+ "try to mmap \"zeros\" again");
+}
diff --git a/src/tests/vm/mmap-overlap.ck b/src/tests/vm/mmap-overlap.ck
new file mode 100644
index 0000000..f13801e
--- /dev/null
+++ b/src/tests/vm/mmap-overlap.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-overlap) begin
+(mmap-overlap) open "zeros" once
+(mmap-overlap) mmap "zeros"
+(mmap-overlap) open "zeros" again
+(mmap-overlap) try to mmap "zeros" again
+(mmap-overlap) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-read.c b/src/tests/vm/mmap-read.c
new file mode 100644
index 0000000..c0f23a1
--- /dev/null
+++ b/src/tests/vm/mmap-read.c
@@ -0,0 +1,32 @@
+/* Uses a memory mapping to read a file. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x10000000;
+ int handle;
+ mapid_t map;
+ size_t i;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ /* Check that data is correct. */
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Verify that data is followed by zeros. */
+ for (i = strlen (sample); i < 4096; i++)
+ if (actual[i] != 0)
+ fail ("byte %zu of mmap'd region has value %02hhx (should be 0)",
+ i, actual[i]);
+
+ munmap (map);
+ close (handle);
+}
diff --git a/src/tests/vm/mmap-read.ck b/src/tests/vm/mmap-read.ck
new file mode 100644
index 0000000..95ab790
--- /dev/null
+++ b/src/tests/vm/mmap-read.ck
@@ -0,0 +1,11 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-read) begin
+(mmap-read) open "sample.txt"
+(mmap-read) mmap "sample.txt"
+(mmap-read) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-remove.c b/src/tests/vm/mmap-remove.c
new file mode 100644
index 0000000..5f7444d
--- /dev/null
+++ b/src/tests/vm/mmap-remove.c
@@ -0,0 +1,43 @@
+/* Deletes and closes file that is mapped into memory
+ and verifies that it can still be read through the mapping. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual = (char *) 0x10000000;
+ int handle;
+ mapid_t map;
+ size_t i;
+
+ /* Map file. */
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ /* Close file and delete it. */
+ close (handle);
+ CHECK (remove ("sample.txt"), "remove \"sample.txt\"");
+ CHECK (open ("sample.txt") == -1, "try to open \"sample.txt\"");
+
+ /* Create a new file in hopes of overwriting data from the old
+ one, in case the file system has incorrectly freed the
+ file's data. */
+ CHECK (create ("another", 4096 * 10), "create \"another\"");
+
+ /* Check that mapped data is correct. */
+ if (memcmp (actual, sample, strlen (sample)))
+ fail ("read of mmap'd file reported bad data");
+
+ /* Verify that data is followed by zeros. */
+ for (i = strlen (sample); i < 4096; i++)
+ if (actual[i] != 0)
+ fail ("byte %zu of mmap'd region has value %02hhx (should be 0)",
+ i, actual[i]);
+
+ munmap (map);
+}
diff --git a/src/tests/vm/mmap-remove.ck b/src/tests/vm/mmap-remove.ck
new file mode 100644
index 0000000..d3cc938
--- /dev/null
+++ b/src/tests/vm/mmap-remove.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-remove) begin
+(mmap-remove) open "sample.txt"
+(mmap-remove) mmap "sample.txt"
+(mmap-remove) remove "sample.txt"
+(mmap-remove) try to open "sample.txt"
+(mmap-remove) create "another"
+(mmap-remove) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-shuffle.c b/src/tests/vm/mmap-shuffle.c
new file mode 100644
index 0000000..29921ad
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.c
@@ -0,0 +1,38 @@
+/* Creates a 128 kB file and repeatedly shuffles data in it
+ through a memory mapping. */
+
+#include <stdio.h>
+#include <string.h>
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (128 * 1024)
+
+static char *buf = (char *) 0x10000000;
+
+void
+test_main (void)
+{
+ size_t i;
+ int handle;
+
+ /* Create file, mmap. */
+ CHECK (create ("buffer", SIZE), "create \"buffer\"");
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ CHECK (mmap (handle, buf) != MAP_FAILED, "mmap \"buffer\"");
+
+ /* Initialize. */
+ for (i = 0; i < SIZE; i++)
+ buf[i] = i * 257;
+ msg ("init: cksum=%lu", cksum (buf, SIZE));
+
+ /* Shuffle repeatedly. */
+ for (i = 0; i < 10; i++)
+ {
+ shuffle (buf, SIZE, 1);
+ msg ("shuffle %zu: cksum=%lu", i, cksum (buf, SIZE));
+ }
+}
diff --git a/src/tests/vm/mmap-shuffle.ck b/src/tests/vm/mmap-shuffle.ck
new file mode 100644
index 0000000..c158301
--- /dev/null
+++ b/src/tests/vm/mmap-shuffle.ck
@@ -0,0 +1,47 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(mmap-shuffle) begin
+(mmap-shuffle) create "buffer"
+(mmap-shuffle) open "buffer"
+(mmap-shuffle) mmap "buffer"
+(mmap-shuffle) init: cksum=$init
+(mmap-shuffle) shuffle 0: cksum=$shuffle[0]
+(mmap-shuffle) shuffle 1: cksum=$shuffle[1]
+(mmap-shuffle) shuffle 2: cksum=$shuffle[2]
+(mmap-shuffle) shuffle 3: cksum=$shuffle[3]
+(mmap-shuffle) shuffle 4: cksum=$shuffle[4]
+(mmap-shuffle) shuffle 5: cksum=$shuffle[5]
+(mmap-shuffle) shuffle 6: cksum=$shuffle[6]
+(mmap-shuffle) shuffle 7: cksum=$shuffle[7]
+(mmap-shuffle) shuffle 8: cksum=$shuffle[8]
+(mmap-shuffle) shuffle 9: cksum=$shuffle[9]
+(mmap-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-twice.c b/src/tests/vm/mmap-twice.c
new file mode 100644
index 0000000..d277a37
--- /dev/null
+++ b/src/tests/vm/mmap-twice.c
@@ -0,0 +1,28 @@
+/* Maps the same file into memory twice and verifies that the
+ same data is readable in both. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *actual[2] = {(char *) 0x10000000, (char *) 0x20000000};
+ size_t i;
+ int handle[2];
+
+ for (i = 0; i < 2; i++)
+ {
+ CHECK ((handle[i] = open ("sample.txt")) > 1,
+ "open \"sample.txt\" #%zu", i);
+ CHECK (mmap (handle[i], actual[i]) != MAP_FAILED,
+ "mmap \"sample.txt\" #%zu at %p", i, (void *) actual[i]);
+ }
+
+ for (i = 0; i < 2; i++)
+ CHECK (!memcmp (actual[i], sample, strlen (sample)),
+ "compare mmap'd file %zu against data", i);
+}
diff --git a/src/tests/vm/mmap-twice.ck b/src/tests/vm/mmap-twice.ck
new file mode 100644
index 0000000..05e9724
--- /dev/null
+++ b/src/tests/vm/mmap-twice.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-twice) begin
+(mmap-twice) open "sample.txt" #0
+(mmap-twice) mmap "sample.txt" #0 at 0x10000000
+(mmap-twice) open "sample.txt" #1
+(mmap-twice) mmap "sample.txt" #1 at 0x20000000
+(mmap-twice) compare mmap'd file 0 against data
+(mmap-twice) compare mmap'd file 1 against data
+(mmap-twice) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-unmap.c b/src/tests/vm/mmap-unmap.c
new file mode 100644
index 0000000..d35a79e
--- /dev/null
+++ b/src/tests/vm/mmap-unmap.c
@@ -0,0 +1,23 @@
+/* Maps and unmaps a file and verifies that the mapped region is
+ inaccessible afterward. */
+
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+
+ munmap (map);
+
+ fail ("unmapped memory is readable (%d)", *(int *) ACTUAL);
+}
diff --git a/src/tests/vm/mmap-unmap.ck b/src/tests/vm/mmap-unmap.ck
new file mode 100644
index 0000000..119658c
--- /dev/null
+++ b/src/tests/vm/mmap-unmap.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('mmap-unmap');
diff --git a/src/tests/vm/mmap-write.c b/src/tests/vm/mmap-write.c
new file mode 100644
index 0000000..46e8043
--- /dev/null
+++ b/src/tests/vm/mmap-write.c
@@ -0,0 +1,32 @@
+/* Writes to a file through a mapping, and unmaps the file,
+ then reads the data in the file back using the read system
+ call to verify. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define ACTUAL ((void *) 0x10000000)
+
+void
+test_main (void)
+{
+ int handle;
+ mapid_t map;
+ char buf[1024];
+
+ /* Write file via mmap. */
+ CHECK (create ("sample.txt", strlen (sample)), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\"");
+ memcpy (ACTUAL, sample, strlen (sample));
+ munmap (map);
+
+ /* Read back via read(). */
+ read (handle, buf, strlen (sample));
+ CHECK (!memcmp (buf, sample, strlen (sample)),
+ "compare read data against written data");
+ close (handle);
+}
diff --git a/src/tests/vm/mmap-write.ck b/src/tests/vm/mmap-write.ck
new file mode 100644
index 0000000..d2c9cc5
--- /dev/null
+++ b/src/tests/vm/mmap-write.ck
@@ -0,0 +1,13 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(mmap-write) begin
+(mmap-write) create "sample.txt"
+(mmap-write) open "sample.txt"
+(mmap-write) mmap "sample.txt"
+(mmap-write) compare read data against written data
+(mmap-write) end
+EOF
+pass;
diff --git a/src/tests/vm/mmap-zero.c b/src/tests/vm/mmap-zero.c
new file mode 100644
index 0000000..368b759
--- /dev/null
+++ b/src/tests/vm/mmap-zero.c
@@ -0,0 +1,27 @@
+/* Tries to map a zero-length file, which may or may not work but
+ should not terminate the process or crash.
+ Then dereferences the address that we tried to map,
+ and the process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char *data = (char *) 0x7f000000;
+ int handle;
+
+ CHECK (create ("empty", 0), "create empty file \"empty\"");
+ CHECK ((handle = open ("empty")) > 1, "open \"empty\"");
+
+ /* Calling mmap() might succeed or fail. We don't care. */
+ msg ("mmap \"empty\"");
+ mmap (handle, data);
+
+ /* Regardless of whether the call worked, *data should cause
+ the process to be terminated. */
+ fail ("unmapped memory is readable (%d)", *data);
+}
+
diff --git a/src/tests/vm/mmap-zero.ck b/src/tests/vm/mmap-zero.ck
new file mode 100644
index 0000000..0130fbd
--- /dev/null
+++ b/src/tests/vm/mmap-zero.ck
@@ -0,0 +1,12 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(mmap-zero) begin
+(mmap-zero) create empty file "empty"
+(mmap-zero) open "empty"
+(mmap-zero) mmap "empty"
+mmap-zero: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/page-linear.c b/src/tests/vm/page-linear.c
new file mode 100644
index 0000000..652a47b
--- /dev/null
+++ b/src/tests/vm/page-linear.c
@@ -0,0 +1,44 @@
+/* Encrypts, then decrypts, 2 MB of memory and verifies that the
+ values are as they should be. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (2 * 1024 * 1024)
+
+static char buf[SIZE];
+
+void
+test_main (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ /* Initialize to 0x5a. */
+ msg ("initialize");
+ memset (buf, 0x5a, sizeof buf);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+
+ /* Encrypt zeros. */
+ msg ("read/modify/write pass one");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Decrypt back to zeros. */
+ msg ("read/modify/write pass two");
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf, SIZE);
+
+ /* Check that it's all 0x5a. */
+ msg ("read pass");
+ for (i = 0; i < SIZE; i++)
+ if (buf[i] != 0x5a)
+ fail ("byte %zu != 0x5a", i);
+}
diff --git a/src/tests/vm/page-linear.ck b/src/tests/vm/page-linear.ck
new file mode 100644
index 0000000..dcbc884
--- /dev/null
+++ b/src/tests/vm/page-linear.ck
@@ -0,0 +1,14 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-linear) begin
+(page-linear) initialize
+(page-linear) read pass
+(page-linear) read/modify/write pass one
+(page-linear) read/modify/write pass two
+(page-linear) read pass
+(page-linear) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-mm.c b/src/tests/vm/page-merge-mm.c
new file mode 100644
index 0000000..908c71c
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort-mm", 80);
+}
diff --git a/src/tests/vm/page-merge-mm.ck b/src/tests/vm/page-merge-mm.ck
new file mode 100644
index 0000000..74fa980
--- /dev/null
+++ b/src/tests/vm/page-merge-mm.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-mm) begin
+(page-merge-mm) init
+(page-merge-mm) sort chunk 0
+(page-merge-mm) sort chunk 1
+(page-merge-mm) sort chunk 2
+(page-merge-mm) sort chunk 3
+(page-merge-mm) sort chunk 4
+(page-merge-mm) sort chunk 5
+(page-merge-mm) sort chunk 6
+(page-merge-mm) sort chunk 7
+(page-merge-mm) wait for child 0
+(page-merge-mm) wait for child 1
+(page-merge-mm) wait for child 2
+(page-merge-mm) wait for child 3
+(page-merge-mm) wait for child 4
+(page-merge-mm) wait for child 5
+(page-merge-mm) wait for child 6
+(page-merge-mm) wait for child 7
+(page-merge-mm) merge
+(page-merge-mm) verify
+(page-merge-mm) success, buf_idx=1,048,576
+(page-merge-mm) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-par.c b/src/tests/vm/page-merge-par.c
new file mode 100644
index 0000000..e7e1609
--- /dev/null
+++ b/src/tests/vm/page-merge-par.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-sort", 123);
+}
diff --git a/src/tests/vm/page-merge-par.ck b/src/tests/vm/page-merge-par.ck
new file mode 100644
index 0000000..31f8aa7
--- /dev/null
+++ b/src/tests/vm/page-merge-par.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-par) begin
+(page-merge-par) init
+(page-merge-par) sort chunk 0
+(page-merge-par) sort chunk 1
+(page-merge-par) sort chunk 2
+(page-merge-par) sort chunk 3
+(page-merge-par) sort chunk 4
+(page-merge-par) sort chunk 5
+(page-merge-par) sort chunk 6
+(page-merge-par) sort chunk 7
+(page-merge-par) wait for child 0
+(page-merge-par) wait for child 1
+(page-merge-par) wait for child 2
+(page-merge-par) wait for child 3
+(page-merge-par) wait for child 4
+(page-merge-par) wait for child 5
+(page-merge-par) wait for child 6
+(page-merge-par) wait for child 7
+(page-merge-par) merge
+(page-merge-par) verify
+(page-merge-par) success, buf_idx=1,048,576
+(page-merge-par) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-seq.c b/src/tests/vm/page-merge-seq.c
new file mode 100644
index 0000000..12e3880
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.c
@@ -0,0 +1,137 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk in
+ sequence. Then we merge the chunks and verify that the result
+ is what it should be. */
+
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+/* This is the max file size for an older version of the Pintos
+ file system that had 126 direct blocks each pointing to a
+ single disk sector. We could raise it now. */
+#define CHUNK_SIZE (126 * 512)
+#define CHUNK_CNT 16 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using a subprocess. */
+static void
+sort_chunks (void)
+{
+ size_t i;
+
+ create ("buffer", CHUNK_SIZE);
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ pid_t child;
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ quiet = true;
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ CHECK ((child = exec ("child-sort buffer")) != -1,
+ "exec \"child-sort buffer\"");
+ CHECK (wait (child) == 123, "wait for child-sort");
+
+ /* Read chunk back from file. */
+ CHECK ((handle = open ("buffer")) > 1, "open \"buffer\"");
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+test_main (void)
+{
+ init ();
+ sort_chunks ();
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/page-merge-seq.ck b/src/tests/vm/page-merge-seq.ck
new file mode 100644
index 0000000..d78f69d
--- /dev/null
+++ b/src/tests/vm/page-merge-seq.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-seq) begin
+(page-merge-seq) init
+(page-merge-seq) sort chunk 0
+(page-merge-seq) sort chunk 1
+(page-merge-seq) sort chunk 2
+(page-merge-seq) sort chunk 3
+(page-merge-seq) sort chunk 4
+(page-merge-seq) sort chunk 5
+(page-merge-seq) sort chunk 6
+(page-merge-seq) sort chunk 7
+(page-merge-seq) sort chunk 8
+(page-merge-seq) sort chunk 9
+(page-merge-seq) sort chunk 10
+(page-merge-seq) sort chunk 11
+(page-merge-seq) sort chunk 12
+(page-merge-seq) sort chunk 13
+(page-merge-seq) sort chunk 14
+(page-merge-seq) sort chunk 15
+(page-merge-seq) merge
+(page-merge-seq) verify
+(page-merge-seq) success, buf_idx=1,032,192
+(page-merge-seq) end
+EOF
+pass;
diff --git a/src/tests/vm/page-merge-stk.c b/src/tests/vm/page-merge-stk.c
new file mode 100644
index 0000000..5eb1069
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.c
@@ -0,0 +1,8 @@
+#include "tests/main.h"
+#include "tests/vm/parallel-merge.h"
+
+void
+test_main (void)
+{
+ parallel_merge ("child-qsort", 72);
+}
diff --git a/src/tests/vm/page-merge-stk.ck b/src/tests/vm/page-merge-stk.ck
new file mode 100644
index 0000000..c5bc1ae
--- /dev/null
+++ b/src/tests/vm/page-merge-stk.ck
@@ -0,0 +1,29 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-merge-stk) begin
+(page-merge-stk) init
+(page-merge-stk) sort chunk 0
+(page-merge-stk) sort chunk 1
+(page-merge-stk) sort chunk 2
+(page-merge-stk) sort chunk 3
+(page-merge-stk) sort chunk 4
+(page-merge-stk) sort chunk 5
+(page-merge-stk) sort chunk 6
+(page-merge-stk) sort chunk 7
+(page-merge-stk) wait for child 0
+(page-merge-stk) wait for child 1
+(page-merge-stk) wait for child 2
+(page-merge-stk) wait for child 3
+(page-merge-stk) wait for child 4
+(page-merge-stk) wait for child 5
+(page-merge-stk) wait for child 6
+(page-merge-stk) wait for child 7
+(page-merge-stk) merge
+(page-merge-stk) verify
+(page-merge-stk) success, buf_idx=1,048,576
+(page-merge-stk) end
+EOF
+pass;
diff --git a/src/tests/vm/page-parallel.c b/src/tests/vm/page-parallel.c
new file mode 100644
index 0000000..9d619e0
--- /dev/null
+++ b/src/tests/vm/page-parallel.c
@@ -0,0 +1,21 @@
+/* Runs 4 child-linear processes at once. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHILD_CNT 4
+
+void
+test_main (void)
+{
+ pid_t children[CHILD_CNT];
+ int i;
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK ((children[i] = exec ("child-linear")) != -1,
+ "exec \"child-linear\"");
+
+ for (i = 0; i < CHILD_CNT; i++)
+ CHECK (wait (children[i]) == 0x42, "wait for child %d", i);
+}
diff --git a/src/tests/vm/page-parallel.ck b/src/tests/vm/page-parallel.ck
new file mode 100644
index 0000000..90c14ef
--- /dev/null
+++ b/src/tests/vm/page-parallel.ck
@@ -0,0 +1,17 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(page-parallel) begin
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) exec "child-linear"
+(page-parallel) wait for child 0
+(page-parallel) wait for child 1
+(page-parallel) wait for child 2
+(page-parallel) wait for child 3
+(page-parallel) end
+EOF
+pass;
diff --git a/src/tests/vm/page-shuffle.c b/src/tests/vm/page-shuffle.c
new file mode 100644
index 0000000..095a9da
--- /dev/null
+++ b/src/tests/vm/page-shuffle.c
@@ -0,0 +1,30 @@
+/* Shuffles a 128 kB data buffer 10 times, printing the checksum
+ after each time. */
+
+#include <stdbool.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define SIZE (128 * 1024)
+
+static char buf[SIZE];
+
+void
+test_main (void)
+{
+ size_t i;
+
+ /* Initialize. */
+ for (i = 0; i < sizeof buf; i++)
+ buf[i] = i * 257;
+ msg ("init: cksum=%lu", cksum (buf, sizeof buf));
+
+ /* Shuffle repeatedly. */
+ for (i = 0; i < 10; i++)
+ {
+ shuffle (buf, sizeof buf, 1);
+ msg ("shuffle %zu: cksum=%lu", i, cksum (buf, sizeof buf));
+ }
+}
diff --git a/src/tests/vm/page-shuffle.ck b/src/tests/vm/page-shuffle.ck
new file mode 100644
index 0000000..6447d38
--- /dev/null
+++ b/src/tests/vm/page-shuffle.ck
@@ -0,0 +1,44 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::cksum;
+use tests::lib;
+
+my ($init, @shuffle);
+if (1) {
+ # Use precalculated values.
+ $init = 3115322833;
+ @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467,
+ 3022003332, 3614934266, 2704001777, 735775156, 1864109763);
+} else {
+ # Recalculate values.
+ my ($buf) = "";
+ for my $i (0...128 * 1024 - 1) {
+ $buf .= chr (($i * 257) & 0xff);
+ }
+ $init = cksum ($buf);
+
+ random_init (0);
+ for my $i (1...10) {
+ $buf = shuffle ($buf, length ($buf), 1);
+ push (@shuffle, cksum ($buf));
+ }
+}
+
+check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]);
+(page-shuffle) begin
+(page-shuffle) init: cksum=$init
+(page-shuffle) shuffle 0: cksum=$shuffle[0]
+(page-shuffle) shuffle 1: cksum=$shuffle[1]
+(page-shuffle) shuffle 2: cksum=$shuffle[2]
+(page-shuffle) shuffle 3: cksum=$shuffle[3]
+(page-shuffle) shuffle 4: cksum=$shuffle[4]
+(page-shuffle) shuffle 5: cksum=$shuffle[5]
+(page-shuffle) shuffle 6: cksum=$shuffle[6]
+(page-shuffle) shuffle 7: cksum=$shuffle[7]
+(page-shuffle) shuffle 8: cksum=$shuffle[8]
+(page-shuffle) shuffle 9: cksum=$shuffle[9]
+(page-shuffle) end
+EOF
+pass;
diff --git a/src/tests/vm/parallel-merge.c b/src/tests/vm/parallel-merge.c
new file mode 100644
index 0000000..cc09bb1
--- /dev/null
+++ b/src/tests/vm/parallel-merge.c
@@ -0,0 +1,149 @@
+/* Generates about 1 MB of random data that is then divided into
+ 16 chunks. A separate subprocess sorts each chunk; the
+ subprocesses run in parallel. Then we merge the chunks and
+ verify that the result is what it should be. */
+
+#include "tests/vm/parallel-merge.h"
+#include <stdio.h>
+#include <syscall.h>
+#include "tests/arc4.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+#define CHUNK_SIZE (128 * 1024)
+#define CHUNK_CNT 8 /* Number of chunks. */
+#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */
+
+unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE];
+size_t histogram[256];
+
+/* Initialize buf1 with random data,
+ then count the number of instances of each value within it. */
+static void
+init (void)
+{
+ struct arc4 arc4;
+ size_t i;
+
+ msg ("init");
+
+ arc4_init (&arc4, "foobar", 6);
+ arc4_crypt (&arc4, buf1, sizeof buf1);
+ for (i = 0; i < sizeof buf1; i++)
+ histogram[buf1[i]]++;
+}
+
+/* Sort each chunk of buf1 using SUBPROCESS,
+ which is expected to return EXIT_STATUS. */
+static void
+sort_chunks (const char *subprocess, int exit_status)
+{
+ pid_t children[CHUNK_CNT];
+ size_t i;
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ char cmd[128];
+ int handle;
+
+ msg ("sort chunk %zu", i);
+
+ /* Write this chunk to a file. */
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ create (fn, CHUNK_SIZE);
+ quiet = true;
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+
+ /* Sort with subprocess. */
+ snprintf (cmd, sizeof cmd, "%s %s", subprocess, fn);
+ CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd);
+ quiet = false;
+ }
+
+ for (i = 0; i < CHUNK_CNT; i++)
+ {
+ char fn[128];
+ int handle;
+
+ CHECK (wait (children[i]) == exit_status, "wait for child %zu", i);
+
+ /* Read chunk back from file. */
+ quiet = true;
+ snprintf (fn, sizeof fn, "buf%zu", i);
+ CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn);
+ read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE);
+ close (handle);
+ quiet = false;
+ }
+}
+
+/* Merge the sorted chunks in buf1 into a fully sorted buf2. */
+static void
+merge (void)
+{
+ unsigned char *mp[CHUNK_CNT];
+ size_t mp_left;
+ unsigned char *op;
+ size_t i;
+
+ msg ("merge");
+
+ /* Initialize merge pointers. */
+ mp_left = CHUNK_CNT;
+ for (i = 0; i < CHUNK_CNT; i++)
+ mp[i] = buf1 + CHUNK_SIZE * i;
+
+ /* Merge. */
+ op = buf2;
+ while (mp_left > 0)
+ {
+ /* Find smallest value. */
+ size_t min = 0;
+ for (i = 1; i < mp_left; i++)
+ if (*mp[i] < *mp[min])
+ min = i;
+
+ /* Append value to buf2. */
+ *op++ = *mp[min];
+
+ /* Advance merge pointer.
+ Delete this chunk from the set if it's emptied. */
+ if ((++mp[min] - buf1) % CHUNK_SIZE == 0)
+ mp[min] = mp[--mp_left];
+ }
+}
+
+static void
+verify (void)
+{
+ size_t buf_idx;
+ size_t hist_idx;
+
+ msg ("verify");
+
+ buf_idx = 0;
+ for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram;
+ hist_idx++)
+ {
+ while (histogram[hist_idx]-- > 0)
+ {
+ if (buf2[buf_idx] != hist_idx)
+ fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx);
+ buf_idx++;
+ }
+ }
+
+ msg ("success, buf_idx=%'zu", buf_idx);
+}
+
+void
+parallel_merge (const char *child_name, int exit_status)
+{
+ init ();
+ sort_chunks (child_name, exit_status);
+ merge ();
+ verify ();
+}
diff --git a/src/tests/vm/parallel-merge.h b/src/tests/vm/parallel-merge.h
new file mode 100644
index 0000000..a6b6431
--- /dev/null
+++ b/src/tests/vm/parallel-merge.h
@@ -0,0 +1,6 @@
+#ifndef TESTS_VM_PARALLEL_MERGE
+#define TESTS_VM_PARALLEL_MERGE 1
+
+void parallel_merge (const char *child_name, int exit_status);
+
+#endif /* tests/vm/parallel-merge.h */
diff --git a/src/tests/vm/process_death.pm b/src/tests/vm/process_death.pm
new file mode 100644
index 0000000..52039a1
--- /dev/null
+++ b/src/tests/vm/process_death.pm
@@ -0,0 +1,22 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+
+sub check_process_death {
+ my ($proc_name) = @_;
+ our ($test);
+ my (@output) = read_text_file ("$test.output");
+
+ common_checks ("run", @output);
+ @output = get_core_output ("run", @output);
+ fail "First line of output is not `($proc_name) begin' message.\n"
+ if $output[0] ne "($proc_name) begin";
+ fail "Output missing '$proc_name: exit(-1)' message.\n"
+ if !grep ("$proc_name: exit(-1)" eq $_, @output);
+ fail "Output contains '($proc_name) end' message.\n"
+ if grep (/\($proc_name\) end/, @output);
+ pass;
+}
+
+1;
diff --git a/src/tests/vm/pt-bad-addr.c b/src/tests/vm/pt-bad-addr.c
new file mode 100644
index 0000000..3ca4084
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.c
@@ -0,0 +1,11 @@
+/* Accesses a bad address.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ fail ("bad addr read as %d", *(int *) 0x04000000);
+}
diff --git a/src/tests/vm/pt-bad-addr.ck b/src/tests/vm/pt-bad-addr.ck
new file mode 100644
index 0000000..09ea039
--- /dev/null
+++ b/src/tests/vm/pt-bad-addr.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('pt-bad-addr');
diff --git a/src/tests/vm/pt-bad-read.c b/src/tests/vm/pt-bad-read.c
new file mode 100644
index 0000000..ee791ff
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.c
@@ -0,0 +1,16 @@
+/* Reads from a file into a bad address.
+ The process must be terminated with -1 exit code. */
+
+#include <syscall.h>
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ read (handle, (char *) &handle - 4096, 1);
+ fail ("survived reading data into bad address");
+}
diff --git a/src/tests/vm/pt-bad-read.ck b/src/tests/vm/pt-bad-read.ck
new file mode 100644
index 0000000..1f96bb4
--- /dev/null
+++ b/src/tests/vm/pt-bad-read.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-bad-read) begin
+(pt-bad-read) open "sample.txt"
+pt-bad-read: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-big-stk-obj.c b/src/tests/vm/pt-big-stk-obj.c
new file mode 100644
index 0000000..6b630ec
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.c
@@ -0,0 +1,20 @@
+/* Allocates and writes to a 64 kB object on the stack.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char stk_obj[65536];
+ struct arc4 arc4;
+
+ arc4_init (&arc4, "foobar", 6);
+ memset (stk_obj, 0, sizeof stk_obj);
+ arc4_crypt (&arc4, stk_obj, sizeof stk_obj);
+ msg ("cksum: %lu", cksum (stk_obj, sizeof stk_obj));
+}
diff --git a/src/tests/vm/pt-big-stk-obj.ck b/src/tests/vm/pt-big-stk-obj.ck
new file mode 100644
index 0000000..eb5853a
--- /dev/null
+++ b/src/tests/vm/pt-big-stk-obj.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-big-stk-obj) begin
+(pt-big-stk-obj) cksum: 3256410166
+(pt-big-stk-obj) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-bad.c b/src/tests/vm/pt-grow-bad.c
new file mode 100644
index 0000000..d4beba2
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.c
@@ -0,0 +1,14 @@
+/* Read from an address 4,096 bytes below the stack pointer.
+ The process must be terminated with -1 exit code. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile ("movl -4096(%esp), %eax");
+}
diff --git a/src/tests/vm/pt-grow-bad.ck b/src/tests/vm/pt-grow-bad.ck
new file mode 100644
index 0000000..4c0ab8a
--- /dev/null
+++ b/src/tests/vm/pt-grow-bad.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']);
+(pt-grow-bad) begin
+pt-grow-bad: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-pusha.c b/src/tests/vm/pt-grow-pusha.c
new file mode 100644
index 0000000..f9762a5
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.c
@@ -0,0 +1,20 @@
+/* Expand the stack by 32 bytes all at once using the PUSHA
+ instruction.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ asm volatile
+ ("movl %%esp, %%eax;" /* Save a copy of the stack pointer. */
+ "andl $0xfffff000, %%esp;" /* Move stack pointer to bottom of page. */
+ "pushal;" /* Push 32 bytes on stack at once. */
+ "movl %%eax, %%esp" /* Restore copied stack pointer. */
+ : : : "eax"); /* Tell GCC we destroyed eax. */
+}
diff --git a/src/tests/vm/pt-grow-pusha.ck b/src/tests/vm/pt-grow-pusha.ck
new file mode 100644
index 0000000..5000966
--- /dev/null
+++ b/src/tests/vm/pt-grow-pusha.ck
@@ -0,0 +1,9 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-pusha) begin
+(pt-grow-pusha) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stack.c b/src/tests/vm/pt-grow-stack.c
new file mode 100644
index 0000000..0997a00
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.c
@@ -0,0 +1,20 @@
+/* Demonstrate that the stack can grow.
+ This must succeed. */
+
+#include <string.h>
+#include "tests/arc4.h"
+#include "tests/cksum.h"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ char stack_obj[4096];
+ struct arc4 arc4;
+
+ arc4_init (&arc4, "foobar", 6);
+ memset (stack_obj, 0, sizeof stack_obj);
+ arc4_crypt (&arc4, stack_obj, sizeof stack_obj);
+ msg ("cksum: %lu", cksum (stack_obj, sizeof stack_obj));
+}
diff --git a/src/tests/vm/pt-grow-stack.ck b/src/tests/vm/pt-grow-stack.ck
new file mode 100644
index 0000000..1e669db
--- /dev/null
+++ b/src/tests/vm/pt-grow-stack.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stack) begin
+(pt-grow-stack) cksum: 3424492700
+(pt-grow-stack) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-grow-stk-sc.c b/src/tests/vm/pt-grow-stk-sc.c
new file mode 100644
index 0000000..3efbb5f
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.c
@@ -0,0 +1,32 @@
+/* This test checks that the stack is properly extended even if
+ the first access to a stack location occurs inside a system
+ call.
+
+ From Godmar Back. */
+
+#include <string.h>
+#include <syscall.h>
+#include "tests/vm/sample.inc"
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+ int slen = strlen (sample);
+ char buf2[65536];
+
+ /* Write file via write(). */
+ CHECK (create ("sample.txt", slen), "create \"sample.txt\"");
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\"");
+ close (handle);
+
+ /* Read back via read(). */
+ CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\"");
+ CHECK (read (handle, buf2 + 32768, slen) == slen, "read \"sample.txt\"");
+
+ CHECK (!memcmp (sample, buf2 + 32768, slen), "compare written data against read data");
+ close (handle);
+}
diff --git a/src/tests/vm/pt-grow-stk-sc.ck b/src/tests/vm/pt-grow-stk-sc.ck
new file mode 100644
index 0000000..23d3b02
--- /dev/null
+++ b/src/tests/vm/pt-grow-stk-sc.ck
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']);
+(pt-grow-stk-sc) begin
+(pt-grow-stk-sc) create "sample.txt"
+(pt-grow-stk-sc) open "sample.txt"
+(pt-grow-stk-sc) write "sample.txt"
+(pt-grow-stk-sc) 2nd open "sample.txt"
+(pt-grow-stk-sc) read "sample.txt"
+(pt-grow-stk-sc) compare written data against read data
+(pt-grow-stk-sc) end
+EOF
+pass;
diff --git a/src/tests/vm/pt-write-code-2.c b/src/tests/vm/pt-write-code-2.c
new file mode 100644
index 0000000..83bcc2c
--- /dev/null
+++ b/src/tests/vm/pt-write-code-2.c
@@ -0,0 +1,15 @@
+/* Try to write to the code segment using a system call.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ int handle;
+
+ CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
+ read (handle, (void *) test_main, 1);
+ fail ("survived reading data into code segment");
+}
diff --git a/src/tests/vm/pt-write-code.c b/src/tests/vm/pt-write-code.c
new file mode 100644
index 0000000..5072cec
--- /dev/null
+++ b/src/tests/vm/pt-write-code.c
@@ -0,0 +1,12 @@
+/* Try to write to the code segment.
+ The process must be terminated with -1 exit code. */
+
+#include "tests/lib.h"
+#include "tests/main.h"
+
+void
+test_main (void)
+{
+ *(int *) test_main = 0;
+ fail ("writing the code segment succeeded");
+}
diff --git a/src/tests/vm/pt-write-code.ck b/src/tests/vm/pt-write-code.ck
new file mode 100644
index 0000000..65610fb
--- /dev/null
+++ b/src/tests/vm/pt-write-code.ck
@@ -0,0 +1,7 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+use tests::vm::process_death;
+
+check_process_death ('pt-write-code');
diff --git a/src/tests/vm/pt-write-code2.ck b/src/tests/vm/pt-write-code2.ck
new file mode 100644
index 0000000..69ffc77
--- /dev/null
+++ b/src/tests/vm/pt-write-code2.ck
@@ -0,0 +1,10 @@
+# -*- perl -*-
+use strict;
+use warnings;
+use tests::tests;
+check_expected ([<<'EOF']);
+(pt-write-code2) begin
+(pt-write-code2) open "sample.txt"
+pt-write-code2: exit(-1)
+EOF
+pass;
diff --git a/src/tests/vm/qsort.c b/src/tests/vm/qsort.c
new file mode 100644
index 0000000..922572c
--- /dev/null
+++ b/src/tests/vm/qsort.c
@@ -0,0 +1,136 @@
+#include "tests/vm/qsort.h"
+#include <stdbool.h>
+#include <debug.h>
+#include <random.h>
+
+/* Picks a pivot for the quicksort from the SIZE bytes in BUF. */
+static unsigned char
+pick_pivot (unsigned char *buf, size_t size)
+{
+ ASSERT (size >= 1);
+ return buf[random_ulong () % size];
+}
+
+/* Checks whether the SIZE bytes in ARRAY are divided into an
+ initial LEFT_SIZE elements all less than PIVOT followed by
+ SIZE - LEFT_SIZE elements all greater than or equal to
+ PIVOT. */
+static bool
+is_partitioned (const unsigned char *array, size_t size,
+ unsigned char pivot, size_t left_size)
+{
+ size_t i;
+
+ for (i = 0; i < left_size; i++)
+ if (array[i] >= pivot)
+ return false;
+
+ for (; i < size; i++)
+ if (array[i] < pivot)
+ return false;
+
+ return true;
+}
+
+/* Swaps the bytes at *A and *B. */
+static void
+swap (unsigned char *a, unsigned char *b)
+{
+ unsigned char t = *a;
+ *a = *b;
+ *b = t;
+}
+
+/* Partitions ARRAY in-place in an initial run of bytes all less
+ than PIVOT, followed by a run of bytes all greater than or
+ equal to PIVOT. Returns the length of the initial run. */
+static size_t
+partition (unsigned char *array, size_t size, int pivot)
+{
+ size_t left_size = size;
+ unsigned char *first = array;
+ unsigned char *last = first + left_size;
+
+ for (;;)
+ {
+ /* Move FIRST forward to point to first element greater than
+ PIVOT. */
+ for (;;)
+ {
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*first >= pivot)
+ break;
+
+ first++;
+ }
+ left_size--;
+
+ /* Move LAST backward to point to last element no bigger
+ than PIVOT. */
+ for (;;)
+ {
+ last--;
+
+ if (first == last)
+ {
+ ASSERT (is_partitioned (array, size, pivot, left_size));
+ return left_size;
+ }
+ else if (*last < pivot)
+ break;
+ else
+ left_size--;
+ }
+
+ /* By swapping FIRST and LAST we extend the starting and
+ ending sequences that pass and fail, respectively,
+ PREDICATE. */
+ swap (first, last);
+ first++;
+ }
+}
+
+/* Returns true if the SIZE bytes in BUF are in nondecreasing
+ order, false otherwise. */
+static bool
+is_sorted (const unsigned char *buf, size_t size)
+{
+ size_t i;
+
+ for (i = 1; i < size; i++)
+ if (buf[i - 1] > buf[i])
+ return false;
+
+ return true;
+}
+
+/* Sorts the SIZE bytes in BUF into nondecreasing order, using
+ the quick-sort algorithm. */
+void
+qsort_bytes (unsigned char *buf, size_t size)
+{
+ if (!is_sorted (buf, size))
+ {
+ int pivot = pick_pivot (buf, size);
+
+ unsigned char *left_half = buf;
+ size_t left_size = partition (buf, size, pivot);
+ unsigned char *right_half = left_half + left_size;
+ size_t right_size = size - left_size;
+
+ if (left_size <= right_size)
+ {
+ qsort_bytes (left_half, left_size);
+ qsort_bytes (right_half, right_size);
+ }
+ else
+ {
+ qsort_bytes (right_half, right_size);
+ qsort_bytes (left_half, left_size);
+ }
+ }
+}
diff --git a/src/tests/vm/qsort.h b/src/tests/vm/qsort.h
new file mode 100644
index 0000000..61b65f3
--- /dev/null
+++ b/src/tests/vm/qsort.h
@@ -0,0 +1,8 @@
+#ifndef TESTS_VM_QSORT_H
+#define TESTS_VM_QSORT_H 1
+
+#include <stddef.h>
+
+void qsort_bytes (unsigned char *buf, size_t size);
+
+#endif /* tests/vm/qsort.h */
diff --git a/src/tests/vm/sample.inc b/src/tests/vm/sample.inc
new file mode 100644
index 0000000..a60a139
--- /dev/null
+++ b/src/tests/vm/sample.inc
@@ -0,0 +1,19 @@
+char sample[] = {
+ "=== ALL USERS PLEASE NOTE ========================\n"
+ "\n"
+ "CAR and CDR now return extra values.\n"
+ "\n"
+ "The function CAR now returns two values. Since it has to go to the\n"
+ "trouble to figure out if the object is carcdr-able anyway, we figured\n"
+ "you might as well get both halves at once. For example, the following\n"
+ "code shows how to destructure a cons (SOME-CONS) into its two slots\n"
+ "(THE-CAR and THE-CDR):\n"
+ "\n"
+ " (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)\n"
+ "\n"
+ "For symmetry with CAR, CDR returns a second value which is the CAR of\n"
+ "the object. In a related change, the functions MAKE-ARRAY and CONS\n"
+ "have been fixed so they don't allocate any storage except on the\n"
+ "stack. This should hopefully help people who don't like using the\n"
+ "garbage collector because it cold boots the machine so often.\n"
+};
diff --git a/src/tests/vm/sample.txt b/src/tests/vm/sample.txt
new file mode 100644
index 0000000..c446830
--- /dev/null
+++ b/src/tests/vm/sample.txt
@@ -0,0 +1,17 @@
+=== ALL USERS PLEASE NOTE ========================
+
+CAR and CDR now return extra values.
+
+The function CAR now returns two values. Since it has to go to the
+trouble to figure out if the object is carcdr-able anyway, we figured
+you might as well get both halves at once. For example, the following
+code shows how to destructure a cons (SOME-CONS) into its two slots
+(THE-CAR and THE-CDR):
+
+ (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)
+
+For symmetry with CAR, CDR returns a second value which is the CAR of
+the object. In a related change, the functions MAKE-ARRAY and CONS
+have been fixed so they don't allocate any storage except on the
+stack. This should hopefully help people who don't like using the
+garbage collector because it cold boots the machine so often.