Repository: Gankra/abi-cafe (language: rust)

Issue:
Change harness protocol to make each write_val include the value index
Currently when a program is sending us values, the only interface it has amounts to `Vec::push`, even though it knows the exact index of every value. This makes desyncs especially painful/annoying. If instead they had `Vec::insert` (without out-of-bounds semantics) we would be able to keep things in sync and clearly identify "hey you never wrote value 3, what the heck".

This would solve

* #42 


Discussion / hints:

Fix patch:
diff --git a/docs/src/harness/run.md b/docs/src/harness/run.md
index 8a4a35e..3e1a008 100644
--- a/docs/src/harness/run.md
+++ b/docs/src/harness/run.md
@@ -13,46 +13,40 @@ uint64_t basic_val(MyStruct arg0, int32_t arg1);
 
 // The test harness will invoke your test through this symbol!
 void do_test(void) {
+    // Declare we're running the first function
+    set_func(CALLER_VALS, 0);
+
     // Initialize and report the inputs
     MyStruct arg0 = { 241, 1234.23 };
-    write_field(CALLER_INPUTS, arg0.field0);
-    write_filed(CALLER_INPUTS, arg0.field1);
-    finished_val(CALLER_INPUTS);
+    write_val(CALLER_VALS, 0, arg0.field0);
+    write_val(CALLER_VALS, 1, arg0.field1);
 
     int32_t arg1 = 5;
-    write_field(CALLER_INPUTS, arg1);
-    finished_val(CALLER_INPUTS);
+    write_val(CALLER_VALS, 2, arg1);
 
     // Do the call
     uint64_t output = basic_val(arg0, arg1);
 
     // Report the output
-    write_field(CALLER_OUTPUTS, output);
-    finished_val(CALLER_OUTPUTS);
-
-    // Declare that the test is complete on our side
-    finished_func(CALLER_INPUTS, CALLER_OUTPUTS);
+    write_val(CALLER_VALS, 3, output);
 }
 ```
 
 ```C
 // Callee Side
 uint64_t basic_val(MyStruct arg0, int32_t arg1) {
+    // Declare we're running the first function
+    set_func(CALLEE_VALS, 0);
+
     // Report the inputs
-    write_field(CALLEE_INPUTS, arg0.field0);
-    write_field(CALLEE_INPUTS, arg0.field1);
-    finished_val(CALLEE_INPUTS);
+    write_val(CALLEE_VALS, 0, arg0.field0);
+    write_val(CALLEE_VALS, 1, arg0.field1);
 
-    write_field(CALLEE_INPUTS, arg1);
-    finished_val(CALLEE_INPUTS);
+    write_val(CALLEE_VALS, 2, arg1);
 
     // Initialize and report the output
     uint64_t output = 17;
-    write_field(CALLEE_OUTPUTS, output);
-    finished_val(CALLEE_OUTPUTS);
-
-    // Declare that the test is complete on our side
-    finished_func(CALLEE_INPUTS, CALLEE_OUTPUTS);
+    write_val(CALLEE_VALS, 3, output);
 
     // Actually return
     return output;
@@ -61,17 +55,17 @@ uint64_t basic_val(MyStruct arg0, int32_t arg1) {
 
 The high level idea is that each side:
 
-* Uses write_field to report individual fields of values (to avoid padding)
-* Uses finished_val to specify that all fields for a value have been written
-* Uses finished_func to specify that the current function is done (the caller will usually contain many subtests, FINISHED_FUNC delimits those)
+* Uses set_func to specify which function we're running
+* Uses write_val to report individual fields of args (to avoid padding)
 
-There are 4 buffers: CALLER_INPUTS, CALLER_OUTPUTS, CALLEE_INPUTS, CALLEE_OUTPUTS. Each side should only use its two buffers.
+There are 2 buffers: CALLER_VALS and CALLEE_VALS
 
 The signatures of the callbacks are as follows, but each language wraps these
 in functions/macros to keep the codegen readable:
 
-* `WRITE(Buffer buffer, char* input, uint32_t size_of_input)`
-* `FINISH_VAL(Buffer buffer)`
-* `FINISH_TEST(Buffer input_buffer, Buffer output_buffer)`
+```c
+SET_FUNC(Buffer buffer, uint32_t func_idx)`
+WRITE_VAL(Buffer buffer, uint32_t val_idx, char* input, uint32_t size_of_input)
+```
 
 Doing things in this very explicit way gives the test harness a better semantic understanding of what the implementations think is happening. This helps us emit better diagnostics and avoid cascading failures between subtests.
diff --git a/include/harness/c/harness_prefix.h b/include/harness/c/harness_prefix.h
index 25adc4c..74fc145 100644
--- a/include/harness/c/harness_prefix.h
+++ b/include/harness/c/harness_prefix.h
@@ -1,14 +1,10 @@
 
 #define WriteBuffer void*
 
-extern WriteBuffer CALLER_INPUTS;
-extern WriteBuffer CALLER_OUTPUTS;
-extern WriteBuffer CALLEE_INPUTS;
-extern WriteBuffer CALLEE_OUTPUTS;
-extern void (*WRITE_FIELD)(WriteBuffer, char*, uint32_t);
-extern void (*FINISHED_VAL)(WriteBuffer);
-extern void (*FINISHED_FUNC)(WriteBuffer, WriteBuffer);
+extern WriteBuffer CALLER_VALS;
+extern WriteBuffer CALLEE_VALS;
+extern void (*WRITE_VAL)(WriteBuffer, uint32_t, char*, uint32_t);
+extern void (*SET_FUNC)(WriteBuffer, uint32_t);
 
-#define finished_val(buffer) FINISHED_VAL(buffer)
-#define finished_func(inputs, outputs) FINISHED_FUNC(inputs, outputs)
-#define write_field(buffer, field) WRITE_FIELD(buffer, (char*)&field, (uint32_t)sizeof(field))
+#define set_func(vals, func_idx) SET_FUNC(vals, func_idx);
+#define write_val(vals, val_idx, val) WRITE_VAL(vals, val_idx, (char*)&val, (uint32_t)sizeof(val))
diff --git a/include/harness/harness.rs b/include/harness/harness.rs
index b97173b..9e6376b 100644
--- a/include/harness/harness.rs
+++ b/include/harness/harness.rs
@@ -15,24 +15,17 @@ pub struct WriteBuffer(*mut ());
 unsafe impl Send for WriteBuffer {}
 unsafe impl Sync for WriteBuffer {}
 
-type WriteCallback = unsafe extern fn(WriteBuffer, *const u8, u32) -> ();
-type FinishedValCallback = unsafe extern fn(WriteBuffer) -> ();
-type FinishedFuncCallback = unsafe extern fn(WriteBuffer, WriteBuffer) -> ();
+type SetFuncCallback = unsafe extern fn(WriteBuffer, u32) -> ();
+type WriteValCallback = unsafe extern fn(WriteBuffer, u32, *const u8, u32) -> ();
 
 #[no_mangle]
-pub static mut CALLER_INPUTS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
+pub static mut CALLER_VALS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
 #[no_mangle]
-pub static mut CALLER_OUTPUTS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
+pub static mut CALLEE_VALS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
 #[no_mangle]
-pub static mut CALLEE_INPUTS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
+pub static mut SET_FUNC: Option<SetFuncCallback> = None;
 #[no_mangle]
-pub static mut CALLEE_OUTPUTS: WriteBuffer = WriteBuffer(core::ptr::null_mut());
-#[no_mangle]
-pub static mut WRITE_FIELD: Option<WriteCallback> = None;
-#[no_mangle]
-pub static mut FINISHED_VAL: Option<FinishedValCallback> = None;
-#[no_mangle]
-pub static mut FINISHED_FUNC: Option<FinishedFuncCallback> = None;
+pub static mut WRITE_VAL: Option<WriteValCallback> = None;
 
 extern {
     fn do_test();
@@ -40,22 +33,16 @@ extern {
 
 #[no_mangle]
 pub extern fn test_start(
-    write_callback: WriteCallback,
-    finished_val_callback: FinishedValCallback,
-    finished_func_callback: FinishedFuncCallback,
-    caller_inputs: WriteBuffer,
-    caller_outputs: WriteBuffer,
-    callee_inputs: WriteBuffer,
-    callee_outputs: WriteBuffer,
+    set_func_callback: SetFuncCallback,
+    write_val_callback: WriteValCallback,
+    caller_vals: WriteBuffer,
+    callee_vals: WriteBuffer,
 ) {
     unsafe {
-        CALLER_INPUTS = caller_inputs;
-        CALLER_OUTPUTS = caller_outputs;
-        CALLEE_INPUTS = callee_inputs;
-        CALLEE_OUTPUTS = callee_outputs;
-        WRITE_FIELD = Some(write_callback);
-        FINISHED_VAL = Some(finished_val_callback);
-        FINISHED_FUNC = Some(finished_func_callback);
+        CALLER_VALS = caller_vals;
+        CALLEE_VALS = callee_vals;
+        SET_FUNC = Some(set_func_callback);
+        WRITE_VAL = Some(write_val_callback);
 
         do_test();
     }
diff --git a/include/harness/rust/harness_prefix.rs b/include/harness/rust/harness_prefix.rs
index 14bcc19..93aece9 100644
--- a/include/harness/rust/harness_prefix.rs
+++ b/include/harness/rust/harness_prefix.rs
@@ -4,30 +4,24 @@ pub struct WriteBuffer(*mut ());
 unsafe impl Send for WriteBuffer {}
 unsafe impl Sync for WriteBuffer {}
 
-type WriteCallback = unsafe extern fn(WriteBuffer, *const u8, u32) -> ();
-type FinishedValCallback = unsafe extern fn(WriteBuffer) -> ();
-type FinishedFuncCallback = unsafe extern fn(WriteBuffer, WriteBuffer) -> ();
+type SetFuncCallback = unsafe extern fn(WriteBuffer, u32) -> ();
+type WriteValCallback = unsafe extern fn(WriteBuffer, u32, *const u8, u32) -> ();
 
 extern {
-    pub static mut CALLER_INPUTS: WriteBuffer;
-    pub static mut CALLER_OUTPUTS: WriteBuffer;
-    pub static mut CALLEE_INPUTS: WriteBuffer;
-    pub static mut CALLEE_OUTPUTS: WriteBuffer;
-    pub static mut WRITE_FIELD: Option<WriteCallback>;
-    pub static mut FINISHED_VAL: Option<FinishedValCallback>;
-    pub static mut FINISHED_FUNC: Option<FinishedFuncCallback>;
+    pub static mut CALLER_VALS: WriteBuffer;
+    pub static mut CALLEE_VALS: WriteBuffer;
+    pub static mut SET_FUNC: Option<SetFuncCallback>;
+    pub static mut WRITE_VAL: Option<WriteValCallback>;
 }
 
-unsafe fn write_field<T>(buffer: WriteBuffer, field: &T) {
-    WRITE_FIELD.unwrap()(
-        buffer,
-        field as *const T as *const u8,
-        core::mem::size_of_val(field) as u32
+unsafe fn write_val<T>(vals: WriteBuffer, val_idx: u32, val: &T) {
+    WRITE_VAL.unwrap()(
+        vals,
+        val_idx,
+        val as *const T as *const u8,
+        core::mem::size_of_val(val) as u32
     );
 }
-unsafe fn finished_val(buffer: WriteBuffer) {
-    FINISHED_VAL.unwrap()(buffer);
-}
-unsafe fn finished_func(inputs: WriteBuffer, outputs: WriteBuffer) {
-    FINISHED_FUNC.unwrap()(inputs, outputs);
+unsafe fn set_func(vals: WriteBuffer, func_idx: u32) {
+    SET_FUNC.unwrap()(vals, func_idx);
 }
diff --git a/src/error.rs b/src/error.rs
index 2d995b4..2c13f1b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -71,30 +71,6 @@ pub enum BuildError {
 #[allow(clippy::enum_variant_names)]
 #[derive(Debug, thiserror::Error, Diagnostic)]
 pub enum CheckFailure {
-    #[error("  {func_name} {arg_kind} count mismatch (expected: {expected_len}, caller: {}, callee: {})
-    caller: {caller:#02X?}
-    callee: {callee:#02X?}", caller.len(), callee.len())]
-    ArgCountMismatch {
-        func_idx: usize,
-        arg_kind: String,
-        func_name: String,
-        expected_len: usize,
-        caller: Vec<Vec<Vec<u8>>>,
-        callee: Vec<Vec<Vec<u8>>>,
-    },
-    #[error("  {func_name} {arg_kind} {arg_name} value count mismatch (expected: {expected_len}, caller: {}, callee: {})
-    caller: {caller:#02X?}
-    callee: {callee:#02X?}", caller.len(), callee.len())]
-    ValCountMismatch {
-        func_idx: usize,
-        arg_idx: usize,
-        arg_kind: String,
-        func_name: String,
-        arg_name: String,
-        expected_len: usize,
-        caller: Vec<Vec<u8>>,
-        callee: Vec<Vec<u8>>,
-    },
     #[error(
         "  {func_name} {arg_kind} differed:
     {arg_kind:<6}: {arg_name}: {arg_ty_name}
@@ -155,6 +131,8 @@ pub enum LinkError {
 pub enum RunError {
     #[error("test loading error (dynamic linking failed)\n{0}")]
     LoadError(#[from] libloading::Error),
-    #[error("wrong number of tests reported! \nExpected {0} \nGot (caller_in: {1}, caller_out: {2}, callee_in: {3}, callee_out: {4})")]
-    TestCountMismatch(usize, usize, usize, usize, usize),
+    #[error("test impl didn't call set_func before calling write_val")]
+    MissingSetFunc,
+    #[error("test impl called write_val on func {func} val {val} twice")]
+    DoubleWrite { func: usize, val: usize },
 }
diff --git a/src/harness/check.rs b/src/harness/check.rs
index caab459..ce23b77 100644
--- a/src/harness/check.rs
+++ b/src/harness/check.rs
@@ -1,9 +1,9 @@
 use console::Style;
+use harness::run::{FuncBuffer, ValBuffer};
 use kdl_script::types::Ty;
 use tracing::{error, info};
 
 use crate::error::*;
-use crate::harness::vals::ArgValuesIter;
 use crate::report::*;
 use crate::*;
 
@@ -12,10 +12,8 @@ impl TestHarness {
         &self,
         key: &TestKey,
         RunOutput {
-            caller_inputs,
-            caller_outputs,
-            callee_inputs,
-            callee_outputs,
+            caller_funcs,
+            callee_funcs,
         }: &RunOutput,
     ) -> CheckOutput {
         let test = self
@@ -23,6 +21,8 @@ impl TestHarness {
             .await
             .expect("check-test called before test_with_vals!?");
         let options = &key.options;
+        let empty_func = FuncBuffer::default();
+        let empty_val = ValBuffer::default();
         // Now check the results
 
         // Start peeling back the layers of the buffers.
@@ -36,110 +36,28 @@ impl TestHarness {
         // Layer 1 is the funcs/subtests. Because we have already checked
         // that they agree on their lengths, we can zip them together
         // to walk through their views of each subtest's execution.
-        'funcs: for (
-            (((&func_idx, caller_inputs), caller_outputs), callee_inputs),
-            callee_outputs,
-        ) in expected_funcs
-            .iter()
-            .zip(&caller_inputs.funcs)
-            .zip(&caller_outputs.funcs)
-            .zip(&callee_inputs.funcs)
-            .zip(&callee_outputs.funcs)
-        {
-            let func = test.types.realize_func(func_idx);
-            let func_name = &func.name;
-            let mut expected_args = test.vals.at_func(func_idx);
-            let mut expected_inputs = vec![];
-            let mut expected_outputs = vec![];
-            for _ in &func.inputs {
-                let arg = expected_args.next_arg();
-                if arg.should_write_arg(options) {
-                    expected_inputs.push((arg.arg_idx, arg));
-                }
-            }
-            for _ in &func.outputs {
-                let arg = expected_args.next_arg();
-                if arg.should_write_arg(options) {
-                    expected_outputs.push((arg.arg_idx, arg));
-                }
-            }
-
-            // Now we must enforce that the caller and callee agree on how
-            // many inputs and outputs there were. If this fails that's a
-            // very fundamental issue, and indicative of a bad test generator.
-            if caller_inputs.len() != expected_inputs.len()
-                || callee_inputs.len() != expected_inputs.len()
-            {
-                results.push(Err(CheckFailure::ArgCountMismatch {
-                    func_idx,
-                    func_name: func_name.to_string(),
-                    arg_kind: "input".to_string(),
-                    expected_len: expected_inputs.len(),
-                    caller: caller_inputs.clone(),
-                    callee: callee_inputs.clone(),
-                }));
-                continue 'funcs;
-            }
-            if caller_outputs.len() != expected_outputs.len()
-                || callee_outputs.len() != expected_outputs.len()
-            {
-                results.push(Err(CheckFailure::ArgCountMismatch {
-                    func_idx,
-                    func_name: func_name.to_string(),
-                    arg_kind: "output".to_string(),
-                    expected_len: expected_inputs.len(),
-                    caller: caller_outputs.clone(),
-                    callee: callee_outputs.clone(),
-                }));
-                continue 'funcs;
-            }
-
-            // Layer 2 is the values (arguments/returns).
-            // The inputs and outputs loop do basically the same work,
-            // but are separate for the sake of error-reporting quality.
-
-            // Process Inputs
-            for (((arg_idx, expected_arg), caller_vals), callee_vals) in expected_inputs
-                .into_iter()
-                .zip(caller_inputs)
-                .zip(callee_inputs)
-            {
-                if let Err(e) = self
-                    .check_vals(
-                        key,
-                        "input",
-                        func_idx,
-                        arg_idx,
-                        expected_arg,
-                        caller_vals,
-                        callee_vals,
-                    )
-                    .await
-                {
-                    results.push(Err(e));
-                    continue 'funcs;
+        'funcs: for func_idx in expected_funcs {
+            let caller_func = caller_funcs.funcs.get(func_idx).unwrap_or(&empty_func);
+            let callee_func = callee_funcs.funcs.get(func_idx).unwrap_or(&empty_func);
+            let mut expected_vals = vec![];
+            for arg in test.vals.at_func(func_idx) {
+                for val in arg {
+                    if val.should_write_val(options) {
+                        expected_vals.push(val);
+                    }
                 }
             }
 
-            // Process Outputs
-            for (((arg_idx, expected_arg), caller_vals), callee_vals) in expected_outputs
-                .into_iter()
-                .zip(caller_outputs)
-                .zip(callee_outputs)
-            {
-                if let Err(e) = self
-                    .check_vals(
-                        key,
-                        "output",
-                        func_idx,
-                        arg_idx,
-                        expected_arg,
-                        caller_vals,
-                        callee_vals,
-                    )
-                    .await
-                {
+            for expected_val in expected_vals {
+                let val_idx = expected_val.absolute_val_idx;
+                let caller_val = caller_func.vals.get(val_idx).unwrap_or(&empty_val);
+                let callee_val = callee_func.vals.get(val_idx).unwrap_or(&empty_val);
+                if let Err(e) = self.check_val(&test, expected_val, caller_val, callee_val) {
                     results.push(Err(e));
+                    // FIXME: now that each value is absolutely indexed,
+                    // we should be able to check all the values independently
+                    // and return all errors. However the first one is the most
+                    // important one, so the UX needs to be worked on...
                     continue 'funcs;
                 }
             }
@@ -190,116 +108,80 @@ impl TestHarness {
     }
 
     #[allow(clippy::too_many_arguments)]
-    async fn check_vals<'a>(
+    fn check_val(
         &self,
-        key: &TestKey,
-        arg_kind: &str,
-        func_idx: usize,
-        arg_idx: usize,
-        mut expected_arg: ArgValuesIter<'a>,
-        caller_vals: &Vec<Vec<u8>>,
-        callee_vals: &Vec<Vec<u8>>,
+        test: &TestWithVals,
+        expected_val: ValueRef,
+        caller_val: &ValBuffer,
+        callee_val: &ValBuffer,
     ) -> Result<(), CheckFailure> {
-        let test = self
-            .test_with_vals(&key.test, key.options.val_generator)
-            .await
-            .expect("check called before test_with_vals!?");
         let types = &test.types;
-        let options = &key.options;
-        let func = types.realize_func(func_idx);
-
-        let mut expected_vals = vec![];
-        let arg_name = &expected_arg.arg().arg_name;
-        let arg_ty = expected_arg.arg().ty;
-        for val_idx in 0..expected_arg.arg().vals.len() {
-            let val = expected_arg.next_val();
-            if val.should_write_val(options) {
-                expected_vals.push((val_idx, val))
-            }
-        }
-        // Now we must enforce that the caller and callee agree on how
-        // many fields each value had.
-        if caller_vals.len() != expected_vals.len() || callee_vals.len() != expected_vals.len() {
-            return Err(CheckFailure::ValCountMismatch {
-                func_idx,
-                arg_idx,
-                arg_kind: arg_kind.to_string(),
-                func_name: func.name.to_string(),
-                arg_name: arg_name.to_string(),
-                expected_len: expected_vals.len(),
-                caller: caller_vals.clone(),
-                callee: callee_vals.clone(),
-            });
-        }
-
-        // Layer 3 is the leaf subfields of the values.
-        // At this point we just need to assert that they agree on the bytes.
-        for (((val_idx, expected_val), caller_val), callee_val) in
-            expected_vals.into_iter().zip(caller_vals).zip(callee_vals)
-        {
-            if let Ty::Tagged(tagged_ty) = types.realize_ty(expected_val.ty) {
-                // This value is "fake" and is actually the semantic tag of tagged union.
-                // In this case showing the bytes doesn't make sense, so show the Variant name
-                // (although we get bytes here they're the array index into the variant,
-                // a purely magical value that only makes sense to the harness itself!).
-                //
-                // Also we use u32::MAX to represent a poison "i dunno what it is, but it's
-                // definitely not the One variant we statically expected!", so most of the
-                // time we're here to print <other variant> and shrug.
-                let expected_tag = expected_val.generate_idx(tagged_ty.variants.len());
-                let caller_tag =
-                    u32::from_ne_bytes(<[u8; 4]>::try_from(&caller_val[..4]).unwrap()) as usize;
-                let callee_tag =
-                    u32::from_ne_bytes(<[u8; 4]>::try_from(&callee_val[..4]).unwrap()) as usize;
-
-                if caller_tag != expected_tag || callee_tag != expected_tag {
-                    let expected = tagged_ty.variants[expected_tag].name.to_string();
-                    let caller = tagged_ty
-                        .variants
-                        .get(caller_tag)
-                        .map(|v| v.name.as_str())
-                        .unwrap_or("<other variant>")
-                        .to_owned();
-                    let callee = tagged_ty
-                        .variants
-                        .get(callee_tag)
-                        .map(|v| v.name.as_str())
-                        .unwrap_or("<other variant>")
-                        .to_owned();
-                    return Err(CheckFailure::TagMismatch {
-                        func_idx,
-                        arg_idx,
-                        val_idx,
-                        arg_kind: arg_kind.to_string(),
-                        func_name: func.name.to_string(),
-                        arg_name: arg_name.to_string(),
-                        arg_ty_name: types.format_ty(arg_ty),
-                        val_path: expected_val.path.to_string(),
-                        val_ty_name: types.format_ty(expected_val.ty),
-                        expected,
-                        caller,
-                        callee,
-                    });
-                }
-            } else if caller_val != callee_val {
-                // Make a buffer with the expected value
-                let mut expected = vec![0; caller_val.len().max(callee_val.len())];
-                expected_val.fill_bytes(&mut expected);
-                return Err(CheckFailure::ValMismatch {
-                    func_idx,
-                    arg_idx,
-                    val_idx,
-                    arg_kind: arg_kind.to_string(),
-                    func_name: func.name.to_string(),
-                    arg_name: arg_name.to_string(),
-                    arg_ty_name: types.format_ty(arg_ty),
+        let func = expected_val.func();
+        let arg = expected_val.arg();
+        if let Ty::Tagged(tagged_ty) = types.realize_ty(expected_val.ty) {
+            // This value is "fake" and is actually the semantic tag of tagged union.
+            // In this case showing the bytes doesn't make sense, so show the Variant name
+            // (although we get bytes here they're the array index into the variant,
+            // a purely magical value that only makes sense to the harness itself!).
+            //
+            // Also we use u32::MAX to represent a poison "i dunno what it is, but it's
+            // definitely not the One variant we statically expected!", so most of the
+            // time we're here to print <other variant> and shrug.
+            let expected_tag = expected_val.generate_idx(tagged_ty.variants.len());
+            let caller_tag =
+                u32::from_ne_bytes(<[u8; 4]>::try_from(&caller_val.bytes[..4]).unwrap()) as usize;
+            let callee_tag =
+                u32::from_ne_bytes(<[u8; 4]>::try_from(&callee_val.bytes[..4]).unwrap()) as usize;
+
+            if caller_tag != expected_tag || callee_tag != expected_tag {
+                let expected = tagged_ty.variants[expected_tag].name.to_string();
+                let caller = tagged_ty
+                    .variants
+                    .get(caller_tag)
+                    .map(|v| v.name.as_str())
+                    .unwrap_or("<other variant>")
+                    .to_owned();
+                let callee = tagged_ty
+                    .variants
+                    .get(callee_tag)
+                    .map(|v| v.name.as_str())
+                    .unwrap_or("<other variant>")
+                    .to_owned();
+                return Err(CheckFailure::TagMismatch {
+                    func_idx: expected_val.func_idx,
+                    arg_idx: expected_val.arg_idx,
+                    val_idx: expected_val.val_idx,
+                    arg_kind: "argument".to_owned(),
+                    func_name: func.func_name.to_string(),
+                    arg_name: arg.arg_name.to_string(),
+                    arg_ty_name: types.format_ty(arg.ty),
                     val_path: expected_val.path.to_string(),
                     val_ty_name: types.format_ty(expected_val.ty),
                     expected,
-                    caller: caller_val.clone(),
-                    callee: callee_val.clone(),
+                    caller,
+                    callee,
                 });
             }
+        } else if caller_val.bytes != callee_val.bytes {
+            // General case, just get a pile of bytes to span both values
+            let mut expected = vec![0; caller_val.bytes.len().max(callee_val.bytes.len())];
+            expected_val.fill_bytes(&mut expected);
+            // FIXME: this doesn't do the right thing for enums
+            // <https://github.com/Gankra/abi-cafe/issues/34>
+            return Err(CheckFailure::ValMismatch {
+                func_idx: expected_val.func_idx,
+                arg_idx: expected_val.arg_idx,
+                val_idx: expected_val.val_idx,
+                arg_kind: "argument".to_owned(),
+                func_name: func.func_name.to_string(),
+                arg_name: arg.arg_name.to_string(),
+                arg_ty_name: types.format_ty(arg.ty),
+                val_path: expected_val.path.to_string(),
+                val_ty_name: types.format_ty(expected_val.ty),
+                expected,
+                caller: caller_val.bytes.clone(),
+                callee: callee_val.bytes.clone(),
+            });
         }
 
         Ok(())
diff --git a/src/harness/mod.rs b/src/harness/mod.rs
index 9225170..b444f3f 100644
--- a/src/harness/mod.rs
+++ b/src/harness/mod.rs
@@ -22,7 +22,7 @@ pub mod test;
 pub mod vals;
 
 pub use read::{find_tests, spawn_read_test};
-pub use run::WriteBuffer;
+pub use run::TestBuffer;
 
 pub type Memoized<K, V> = Mutex<SortedMap<K, Arc<OnceCell<V>>>>;
 
diff --git a/src/harness/run.rs b/src/harness/run.rs
index df41c47..0d8e4f9 100644
--- a/src/harness/run.rs
+++ b/src/harness/run.rs
@@ -16,124 +16,97 @@ impl TestHarness {
     ) -> Result<RunOutput, RunError> {
         let full_test_name = self.full_test_name(key);
         let output = run_dynamic_test(test_dylib, &full_test_name)?;
-        self.check_ran_all(key, &output)?;
         Ok(output)
     }
-
-    fn check_ran_all(
-        &self,
-        key: &TestKey,
-        RunOutput {
-            caller_inputs,
-            caller_outputs,
-            callee_inputs,
-            callee_outputs,
-        }: &RunOutput,
-    ) -> Result<(), RunError> {
-        let test = &self.tests[&key.test];
-        // As a basic sanity-check, make sure everything agrees on how
-        // many tests actually executed. If this fails, then something
-        // is very fundamentally broken and needs to be fixed.
-        let expected_funcs = key.options.functions.active_funcs(&test.types);
-        let expected_test_count = expected_funcs.len();
-        if caller_inputs.funcs.len() != expected_test_count
-            || caller_outputs.funcs.len() != expected_test_count
-            || callee_inputs.funcs.len() != expected_test_count
-            || callee_outputs.funcs.len() != expected_test_count
-        {
-            return Err(RunError::TestCountMismatch(
-                expected_test_count,
-                caller_inputs.funcs.len(),
-                caller_outputs.funcs.len(),
-                callee_inputs.funcs.len(),
-                callee_outputs.funcs.len(),
-            ));
-        }
-        Ok(())
-    }
 }
 
 /// Tests write back the raw bytes of their values to a WriteBuffer.
-///
-/// This hierarchical design is confusing as hell, but represents the
-/// nested levels of abstraction we are concerned with:
-///
-/// subtests (functions) => values (args/returns) => subfields => bytes.
-///
-/// Having this much hierarchy means that we can specifically say
-/// "ah yeah, on test 3 the two sides disagreed on arg2.field1.field2"
-/// and also reduces the chance of failures in one test "cascading"
-/// into the subsequent ones.
 #[derive(Debug, Serialize)]
-pub struct WriteBuffer {
-    pub funcs: Vec<Vec<Vec<Vec<u8>>>>,
+pub struct TestBuffer {
+    pub funcs: Vec<FuncBuffer>,
+    pub cur_func: Option<usize>,
+    pub had_missing_set_func: bool,
+    pub had_double_writes: Vec<(usize, usize)>,
+}
+
+#[derive(Debug, Serialize, Default)]
+pub struct FuncBuffer {
+    pub vals: Vec<ValBuffer>,
 }
 
-impl WriteBuffer {
+#[derive(Debug, Serialize, Default)]
+pub struct ValBuffer {
+    pub bytes: Vec<u8>,
+}
+
+impl TestBuffer {
     fn new() -> Self {
         // Preload the hierarchy for the first test.
-        WriteBuffer {
-            funcs: vec![vec![vec![]]],
+        TestBuffer {
+            funcs: vec![],
+            cur_func: None,
+            had_missing_set_func: false,
+            had_double_writes: vec![],
         }
     }
-    fn finish_tests(&mut self) {
-        // Remove the pending test
-        self.funcs.pop();
+    fn finish_tests(&mut self) -> Result<(), RunError> {
+        if self.had_missing_set_func {
+            return Err(RunError::MissingSetFunc);
+        }
+        if let Some(&(func, val)) = self.had_double_writes.first() {
+            return Err(RunError::DoubleWrite { func, val });
+        }
+
+        Ok(())
     }
 }
 
 // The signatures of the interface from our perspective.
 // From the test's perspective the WriteBuffers are totally opaque.
-pub type WriteCallback = unsafe extern "C" fn(&mut WriteBuffer, *const u8, u32) -> ();
-pub type FinishedValCallback = unsafe extern "C" fn(&mut WriteBuffer) -> ();
-pub type FinishedFuncCallback = unsafe extern "C" fn(&mut WriteBuffer, &mut WriteBuffer) -> ();
-pub type TestInit = unsafe extern "C" fn(
-    WriteCallback,
-    FinishedValCallback,
-    FinishedFuncCallback,
-    &mut WriteBuffer,
-    &mut WriteBuffer,
-    &mut WriteBuffer,
-    &mut WriteBuffer,
-) -> ();
-
-pub unsafe extern "C" fn write_field(output: &mut WriteBuffer, input: *const u8, size: u32) {
-    // Push the bytes of an individual field
-    let data = std::slice::from_raw_parts(input, size as usize);
-    output
-        .funcs
-        .last_mut() // values
-        .unwrap()
-        .last_mut() // fields
-        .unwrap()
-        .push(data.to_vec());
-}
-pub unsafe extern "C" fn finished_val(output: &mut WriteBuffer) {
-    // This value is finished, push a new entry
-    output
-        .funcs
-        .last_mut() // values
-        .unwrap()
-        .push(vec![]);
+pub type SetFuncCallback = unsafe extern "C" fn(&mut TestBuffer, u32) -> ();
+pub type WriteValCallback = unsafe extern "C" fn(&mut TestBuffer, u32, *const u8, u32) -> ();
+pub type TestInit =
+    unsafe extern "C" fn(SetFuncCallback, WriteValCallback, &mut TestBuffer, &mut TestBuffer) -> ();
+
+pub unsafe extern "C" fn set_func(test: &mut TestBuffer, func: u32) {
+    let idx = func as usize;
+    // If things aren't in-order, add empty entries to make the index exist
+    let new_len = test.funcs.len().max(idx + 1);
+    test.funcs
+        .resize_with(new_len, || FuncBuffer { vals: vec![] });
+    test.cur_func = Some(idx);
 }
-pub unsafe extern "C" fn finished_func(output1: &mut WriteBuffer, output2: &mut WriteBuffer) {
-    // Remove the pending value
-    output1
-        .funcs
-        .last_mut() // values
-        .unwrap()
-        .pop()
-        .unwrap();
-    output2
+
+pub unsafe extern "C" fn write_val(
+    test: &mut TestBuffer,
+    val_idx: u32,
+    input: *const u8,
+    size: u32,
+) {
+    // Get the current function
+    let Some(func_idx) = test.cur_func else {
+        test.had_missing_set_func = true;
+        return;
+    };
+    let func = test
         .funcs
-        .last_mut() // values
-        .unwrap()
-        .pop()
-        .unwrap();
-
-    // Push a new pending function
-    output1.funcs.push(vec![vec![]]);
-    output2.funcs.push(vec![vec![]]);
+        .get_mut(func_idx)
+        .expect("harness corrupted its own func idx!?");
+
+    // Get the value of the function (making room for it if need be)
+    let val_idx = val_idx as usize;
+    let new_len = func.vals.len().max(val_idx + 1);
+    func.vals
+        .resize_with(new_len, || ValBuffer { bytes: vec![] });
+    let val = &mut func.vals[val_idx];
+
+    // Push all the bytes of the value
+    if !val.bytes.is_empty() {
+        test.had_double_writes.push((func_idx, val_idx));
+        return;
+    }
+    let data = std::slice::from_raw_parts(input, size as usize);
+    val.bytes = data.to_vec();
 }
 
 /// Run the test!
@@ -141,10 +114,8 @@ pub unsafe extern "C" fn finished_func(output1: &mut WriteBuffer, output2: &mut
 /// See the README for a high-level description of this design.
 fn run_dynamic_test(test_dylib: &LinkOutput, _full_test_name: &str) -> Result<RunOutput, RunError> {
     // Initialize all the buffers the tests will write to
-    let mut caller_inputs = WriteBuffer::new();
-    let mut caller_outputs = WriteBuffer::new();
-    let mut callee_inputs = WriteBuffer::new();
-    let mut callee_outputs = WriteBuffer::new();
+    let mut caller_vals = TestBuffer::new();
+    let mut callee_vals = TestBuffer::new();
 
     unsafe {
         info!("running     {}", test_dylib.test_bin.file_name().unwrap());
@@ -154,27 +125,15 @@ fn run_dynamic_test(test_dylib: &LinkOutput, _full_test_name: &str) -> Result<Ru
         let do_test: libloading::Symbol<TestInit> = lib.get(b"test_start")?;
         debug!("calling harness dynamic function");
         // Actually run the test!
-        do_test(
-            write_field,
-            finished_val,
-            finished_func,
-            &mut caller_inputs,
-            &mut caller_outputs,
-            &mut callee_inputs,
-            &mut callee_outputs,
-        );
+        do_test(set_func, write_val, &mut caller_vals, &mut callee_vals);
 
         // Finalize the buffers (clear all the pending values).
-        caller_inputs.finish_tests();
-        caller_outputs.finish_tests();
-        callee_inputs.finish_tests();
-        callee_outputs.finish_tests();
+        caller_vals.finish_tests()?;
+        callee_vals.finish_tests()?;
     }
 
     Ok(RunOutput {
-        caller_inputs,
-        caller_outputs,
-        callee_inputs,
-        callee_outputs,
+        caller_funcs: caller_vals,
+        callee_funcs: callee_vals,
     })
 }
diff --git a/src/harness/vals.rs b/src/harness/vals.rs
index 41caab4..1bf7bfe 100644
--- a/src/harness/vals.rs
+++ b/src/harness/vals.rs
@@ -16,6 +16,7 @@ pub struct ValueTree {
 
 #[derive(Debug, Clone)]
 pub struct FuncValues {
+    pub func_name: String,
     pub args: Vec<ArgValues>,
 }
 
@@ -24,6 +25,7 @@ pub struct ArgValues {
     pub arg_name: String,
     pub ty: TyIdx,
     pub vals: Vec<Value>,
+    pub absolute_val_idx_start: usize,
 }
 
 #[derive(Debug, Clone)]
@@ -43,10 +45,11 @@ pub struct ArgValuesIter<'a> {
 
 #[derive(Debug, Clone)]
 pub struct ValueRef<'a> {
-    tree: &'a ValueTree,
-    func_idx: usize,
-    arg_idx: usize,
-    val_idx: usize,
+    pub tree: &'a ValueTree,
+    pub func_idx: usize,
+    pub arg_idx: usize,
+    pub val_idx: usize,
+    pub absolute_val_idx: usize,
 }
 
 #[derive(Debug, Clone)]
@@ -114,6 +117,8 @@ impl ValueTree {
             .all_funcs()
             .map(|func_idx| {
                 let func = types.realize_func(func_idx);
+                let func_name = func.name.to_string();
+                let mut absolute_val_idx_start = 0;
                 let args = func
                     .inputs
                     .iter()
@@ -122,14 +127,17 @@ impl ValueTree {
                         let mut vals = vec![];
                         let arg_name = arg.name.to_string();
                         generators.build_values(types, arg.ty, &mut vals, arg_name.clone())?;
-                        Ok(ArgValues {
+                        let res = ArgValues {
                             ty: arg.ty,
                             arg_name,
                             vals,
-                        })
+                            absolute_val_idx_start,
+                        };
+                        absolute_val_idx_start += res.vals.len();
+                        Ok(res)
                     })
                     .collect::<Result<Vec<_>, GenerateError>>()?;
-                Ok(FuncValues { args })
+                Ok(FuncValues { func_name, args })
             })
             .collect::<Result<Vec<_>, GenerateError>>()?;
 
@@ -174,6 +182,17 @@ impl<'a> FuncValuesIter<'a> {
         }
     }
 }
+impl<'a> Iterator for FuncValuesIter<'a> {
+    type Item = ArgValuesIter<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.arg_idx < self.tree.funcs[self.func_idx].args.len() {
+            Some(self.next_arg())
+        } else {
+            None
+        }
+    }
+}
 
 impl<'a> ArgValuesIter<'a> {
     #[track_caller]
@@ -194,6 +213,7 @@ impl<'a> ArgValuesIter<'a> {
             func_idx,
             arg_idx,
             val_idx,
+            absolute_val_idx: self.arg().absolute_val_idx_start + val_idx,
         }
     }
 
@@ -208,12 +228,30 @@ impl<'a> ArgValuesIter<'a> {
     }
 }
 
+impl<'a> Iterator for ArgValuesIter<'a> {
+    type Item = ValueRef<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.val_idx < self.tree.funcs[self.func_idx].args[self.arg_idx].vals.len() {
+            Some(self.next_val())
+        } else {
+            None
+        }
+    }
+}
+
 impl<'a> ValueRef<'a> {
     pub fn should_write_val(&self, options: &TestOptions) -> bool {
         options
             .functions
             .should_write_val(self.func_idx, self.arg_idx, self.val_idx)
     }
+    pub fn arg(&self) -> &'a ArgValues {
+        &self.tree.funcs[self.func_idx].args[self.arg_idx]
+    }
+    pub fn func(&self) -> &'a FuncValues {
+        &self.tree.funcs[self.func_idx]
+    }
 }
 impl<'a> std::ops::Deref for ValueRef<'a> {
     type Target = Value;
diff --git a/src/main.rs b/src/main.rs
index 2ae7ea9..f7fe651 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -246,19 +246,6 @@ fn generate_minimized_failures(
                     return None;
                 };
                 let functions = match *failure {
-                    CheckFailure::ArgCountMismatch { func_idx, .. } => FunctionSelector::One {
-                        idx: func_idx,
-                        args: ArgSelector::All,
-                    },
-                    CheckFailure::ValCountMismatch {
-                        func_idx, arg_idx, ..
-                    } => FunctionSelector::One {
-                        idx: func_idx,
-                        args: ArgSelector::One {
-                            idx: arg_idx,
-                            vals: ValSelector::All,
-                        },
-                    },
                     CheckFailure::ValMismatch {
                         func_idx,
                         arg_idx,
diff --git a/src/report.rs b/src/report.rs
index 9422e8c..15e4c1e 100644
--- a/src/report.rs
+++ b/src/report.rs
@@ -104,13 +104,9 @@ impl Serialize for GenerateError {
 #[derive(Debug, Serialize)]
 pub struct RunOutput {
     #[serde(skip)]
-    pub caller_inputs: WriteBuffer,
+    pub caller_funcs: TestBuffer,
     #[serde(skip)]
-    pub caller_outputs: WriteBuffer,
-    #[serde(skip)]
-    pub callee_inputs: WriteBuffer,
-    #[serde(skip)]
-    pub callee_outputs: WriteBuffer,
+    pub callee_funcs: TestBuffer,
 }
 
 pub fn report_test(results: TestRunResults) -> TestReport {
diff --git a/src/toolchains/c.rs b/src/toolchains/c.rs
index 08d80f2..e1759ba 100644
--- a/src/toolchains/c.rs
+++ b/src/toolchains/c.rs
@@ -16,10 +16,8 @@ use super::*;
 use crate::fivemat::Fivemat;
 use crate::harness::vals::ArgValuesIter;
 
-const VAR_CALLER_INPUTS: &str = "CALLER_INPUTS";
-const VAR_CALLER_OUTPUTS: &str = "CALLER_OUTPUTS";
-const VAR_CALLEE_INPUTS: &str = "CALLEE_INPUTS";
-const VAR_CALLEE_OUTPUTS: &str = "CALLEE_OUTPUTS";
+const CALLER_VALS: &str = "CALLER_VALS";
+const CALLEE_VALS: &str = "CALLEE_VALS";
 const INDENT: &str = "    ";
 
 pub struct CcToolchain {
@@ -155,13 +153,16 @@ impl CcToolchain {
         f.add_indent(1);
         let function = state.types.realize_func(func);
 
+        // Report we're starting a function
+        self.write_set_function(f, state, CALLER_VALS, func)?;
+
         // Create vars for all the inputs
         let mut func_vals = state.vals.at_func(func);
         for arg in &function.inputs {
             let arg_vals: ArgValuesIter = func_vals.next_arg();
             // Create and report the input
             self.init_var(f, state, &arg.name, arg.ty, arg_vals.clone())?;
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLER_INPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLER_VALS)?;
         }
 
         // Call the function
@@ -171,11 +172,9 @@ impl CcToolchain {
         for arg in &function.outputs {
             let arg_vals: ArgValuesIter = func_vals.next_arg();
 
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLER_OUTPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLER_VALS)?;
         }
 
-        // Report the function is complete
-        self.write_end_function(f, state, VAR_CALLER_INPUTS, VAR_CALLER_OUTPUTS)?;
         f.sub_indent(1);
         writeln!(f, "}}")?;
         Ok(())
@@ -239,24 +238,24 @@ impl CcToolchain {
         writeln!(f, " {{")?;
         f.add_indent(1);
 
+        // Report we're starting a function
+        self.write_set_function(f, state, CALLEE_VALS, func)?;
+
         // Report the inputs
         let mut func_vals = state.vals.at_func(func);
         for arg in &function.inputs {
             let arg_vals = func_vals.next_arg();
             let arg_name = &arg.name;
-            self.write_var(f, state, arg_name, arg.ty, arg_vals, VAR_CALLEE_INPUTS)?;
+            self.write_var(f, state, arg_name, arg.ty, arg_vals, CALLEE_VALS)?;
         }
 
         // Create outputs and report them
         for arg in &function.outputs {
             let arg_vals = func_vals.next_arg();
             self.init_var(f, state, &arg.name, arg.ty, arg_vals.clone())?;
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLEE_OUTPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLEE_VALS)?;
         }
 
-        // Report the function is complete
-        self.write_end_function(f, state, VAR_CALLEE_INPUTS, VAR_CALLEE_OUTPUTS)?;
-
         // Return the outputs
         self.check_returns(state, function)?;
         if let Some(arg) = function.outputs.first() {
diff --git a/src/toolchains/c/write.rs b/src/toolchains/c/write.rs
index 8e07ee9..3eb9d99 100644
--- a/src/toolchains/c/write.rs
+++ b/src/toolchains/c/write.rs
@@ -1,5 +1,4 @@
 use super::*;
-use crate::harness::vals::Value;
 use kdl_script::types::{Ty, TyIdx};
 use std::fmt::Write;
 
@@ -51,11 +50,6 @@ impl CcToolchain {
         };
         self.write_fields(f, state, to, var_name, var_ty, &mut vals)?;
 
-        // If doing full harness callbacks, signal we wrote all the fields of a variable
-        if let WriteImpl::HarnessCallback = state.options.val_writer {
-            writeln!(f, "finished_val({to});")?;
-            writeln!(f)?;
-        }
         Ok(())
     }
 
@@ -190,7 +184,7 @@ impl CcToolchain {
                 let tag_generator = vals.next_val();
                 let tag_idx = tag_generator.generate_idx(union_ty.fields.len());
                 if tag_generator.should_write_val(&state.options) {
-                    self.write_tag_field(f, state, to, tag_idx)?;
+                    self.write_tag_field(f, state, to, tag_idx, &tag_generator)?;
                 }
                 if let Some(field) = union_ty.fields.get(tag_idx) {
                     let field_name = &field.ident;
@@ -209,16 +203,18 @@ impl CcToolchain {
         state: &TestState,
         to: &str,
         path: &str,
-        val: &Value,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
+                let val_idx = val.absolute_val_idx;
                 // Convenience for triggering test failures
-                if path.contains("abicafepoison") && to.contains(VAR_CALLEE_INPUTS) {
-                    writeln!(f, "write_field({to}, (uint32_t)0x12345678);")?;
+                let rvalue = if path.contains("abicafepoison") && to.contains(CALLEE_VALS) {
+                    "(uint32_t)0x12345678"
                 } else {
-                    writeln!(f, "write_field({to}, {path});")?;
-                }
+                    path
+                };
+                writeln!(f, "write_val({to}, {val_idx}, {rvalue});")?;
             }
             WriteImpl::Assert => {
                 write!(f, "assert_eq({path}, ")?;
@@ -241,13 +237,15 @@ impl CcToolchain {
         state: &TestState,
         to: &str,
         variant_idx: usize,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
+                let val_idx = val.absolute_val_idx;
                 writeln!(f, "{{")?;
                 f.add_indent(1);
                 writeln!(f, "uint32_t _temp = {};", variant_idx)?;
-                writeln!(f, "write_field({to}, _temp);")?;
+                writeln!(f, "write_val({to}, {val_idx}, _temp);")?;
                 f.sub_indent(1);
                 writeln!(f, "}}")?;
             }
@@ -270,13 +268,15 @@ impl CcToolchain {
         f: &mut Fivemat,
         state: &TestState,
         to: &str,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
+                let val_idx = val.absolute_val_idx;
                 writeln!(f, "{{")?;
                 f.add_indent(1);
                 writeln!(f, "uint32_t _temp = {};", u32::MAX)?;
-                writeln!(f, "write_field({to}, _temp);")?;
+                writeln!(f, "write_va;({to}, {val_idx}, _temp);")?;
                 f.sub_indent(1);
                 writeln!(f, "}}")?;
             }
@@ -290,16 +290,16 @@ impl CcToolchain {
         Ok(())
     }
 
-    pub fn write_end_function(
+    pub fn write_set_function(
         &self,
         f: &mut dyn Write,
         state: &TestState,
-        inputs: &str,
-        outputs: &str,
+        vals: &str,
+        idx: usize,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
-                writeln!(f, "finished_func({inputs}, {outputs});")?;
+                writeln!(f, "set_func({vals}, {idx});")?;
             }
             WriteImpl::Print | WriteImpl::Noop | WriteImpl::Assert => {
                 // Noop
diff --git a/src/toolchains/rust.rs b/src/toolchains/rust.rs
index c193c4f..0c50f7a 100644
--- a/src/toolchains/rust.rs
+++ b/src/toolchains/rust.rs
@@ -16,10 +16,8 @@ use super::*;
 use crate::fivemat::Fivemat;
 use crate::vals::ArgValuesIter;
 
-const VAR_CALLER_INPUTS: &str = "CALLER_INPUTS";
-const VAR_CALLER_OUTPUTS: &str = "CALLER_OUTPUTS";
-const VAR_CALLEE_INPUTS: &str = "CALLEE_INPUTS";
-const VAR_CALLEE_OUTPUTS: &str = "CALLEE_OUTPUTS";
+const CALLER_VALS: &str = "CALLER_VALS";
+const CALLEE_VALS: &str = "CALLEE_VALS";
 const INDENT: &str = "    ";
 
 pub struct TestState {
@@ -155,13 +153,16 @@ impl RustcToolchain {
         f.add_indent(1);
         let function = state.types.realize_func(func);
 
+        // Report we're starting a function
+        self.write_set_function(f, state, CALLER_VALS, func)?;
+
         // Create vars for all the inputs
         let mut func_vals = state.vals.at_func(func);
         for arg in &function.inputs {
             let arg_vals: ArgValuesIter = func_vals.next_arg();
             // Create and report the input
             self.init_var(f, state, &arg.name, arg.ty, arg_vals.clone())?;
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLER_INPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLER_VALS)?;
         }
 
         // Call the function
@@ -171,11 +172,9 @@ impl RustcToolchain {
         for arg in &function.outputs {
             let arg_vals: ArgValuesIter = func_vals.next_arg();
 
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLER_OUTPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLER_VALS)?;
         }
 
-        // Report the function is complete
-        self.write_end_function(f, state, VAR_CALLER_INPUTS, VAR_CALLER_OUTPUTS)?;
         f.sub_indent(1);
         writeln!(f, "}}")?;
         Ok(())
@@ -243,24 +242,24 @@ impl RustcToolchain {
         writeln!(f, "unsafe {{")?;
         f.add_indent(1);
 
+        // Report we're starting a function
+        self.write_set_function(f, state, CALLEE_VALS, func)?;
+
         // Report the inputs
         let mut func_vals = state.vals.at_func(func);
         for arg in &function.inputs {
             let arg_vals = func_vals.next_arg();
             let arg_name = &arg.name;
-            self.write_var(f, state, arg_name, arg.ty, arg_vals, VAR_CALLEE_INPUTS)?;
+            self.write_var(f, state, arg_name, arg.ty, arg_vals, CALLEE_VALS)?;
         }
 
         // Create outputs and report them
         for arg in &function.outputs {
             let arg_vals = func_vals.next_arg();
             self.init_var(f, state, &arg.name, arg.ty, arg_vals.clone())?;
-            self.write_var(f, state, &arg.name, arg.ty, arg_vals, VAR_CALLEE_OUTPUTS)?;
+            self.write_var(f, state, &arg.name, arg.ty, arg_vals, CALLEE_VALS)?;
         }
 
-        // Report the function is complete
-        self.write_end_function(f, state, VAR_CALLEE_INPUTS, VAR_CALLEE_OUTPUTS)?;
-
         // Return the outputs
         self.check_returns(state, function)?;
         if let Some(arg) = function.outputs.first() {
diff --git a/src/toolchains/rust/write.rs b/src/toolchains/rust/write.rs
index a15a309..7e5b5d8 100644
--- a/src/toolchains/rust/write.rs
+++ b/src/toolchains/rust/write.rs
@@ -1,6 +1,5 @@
 use super::*;
-use crate::harness::vals::Value;
-use kdl_script::types::{PrimitiveTy, Ty, TyIdx};
+use kdl_script::types::{Ty, TyIdx};
 use std::fmt::Write;
 
 impl RustcToolchain {
@@ -71,11 +70,6 @@ impl RustcToolchain {
         };
         self.write_fields(f, state, to, var_name, var_ty, &mut vals)?;
 
-        // If doing full harness callbacks, signal we wrote all the fields of a variable
-        if let WriteImpl::HarnessCallback = state.options.val_writer {
-            writeln!(f, "finished_val({to});")?;
-            writeln!(f)?;
-        }
         Ok(())
     }
 
@@ -157,7 +151,7 @@ impl RustcToolchain {
                         let f = &mut Fivemat::new(&mut temp_out, INDENT);
                         f.add_indent(1);
                         if tag_generator.should_write_val(&state.options) {
-                            self.write_tag_field(f, state, to, tag_idx)?;
+                            self.write_tag_field(f, state, to, tag_idx, &tag_generator)?;
                         }
                         if let Some(fields) = &variant.fields {
                             for field in fields {
@@ -175,7 +169,7 @@ impl RustcToolchain {
                         let f = &mut Fivemat::new(&mut temp_out, INDENT);
                         f.add_indent(1);
                         if tag_generator.should_write_val(&state.options) {
-                            self.write_error_tag_field(f, state, to)?;
+                            self.write_error_tag_field(f, state, to, &tag_generator)?;
                         }
                         f.sub_indent(1);
                         temp_out
@@ -205,7 +199,7 @@ impl RustcToolchain {
                 let tag_generator = vals.next_val();
                 let tag_idx = tag_generator.generate_idx(union_ty.fields.len());
                 if tag_generator.should_write_val(&state.options) {
-                    self.write_tag_field(f, state, to, tag_idx)?;
+                    self.write_tag_field(f, state, to, tag_idx, &tag_generator)?;
                 }
                 if let Some(field) = union_ty.fields.get(tag_idx) {
                     let field_name = &field.ident;
@@ -224,16 +218,18 @@ impl RustcToolchain {
         state: &TestState,
         to: &str,
         path: &str,
-        val: &Value,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
+                let val_idx = val.absolute_val_idx;
                 // Convenience for triggering test failures
-                if path.contains("abicafepoison") && to.contains(VAR_CALLEE_INPUTS) {
-                    writeln!(f, "write_field({to}, &0x12345678u32);")?;
+                let rvalue = if path.contains("abicafepoison") && to.contains(CALLEE_VALS) {
+                    "0x12345678u32"
                 } else {
-                    writeln!(f, "write_field({to}, &{path});")?;
-                }
+                    path
+                };
+                writeln!(f, "write_val({to}, {val_idx}, &{rvalue});")?;
             }
             WriteImpl::Assert => {
                 write!(f, "assert_eq!({path}, ")?;
@@ -256,10 +252,12 @@ impl RustcToolchain {
         state: &TestState,
         to: &str,
         variant_idx: usize,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
-                writeln!(f, "write_field({to}, &{}u32);", variant_idx)?;
+                let val_idx = val.absolute_val_idx;
+                writeln!(f, "write_val({to}, {val_idx}, &{}u32);", variant_idx)?;
             }
             WriteImpl::Assert => {
                 // Noop, do nothing
@@ -279,10 +277,12 @@ impl RustcToolchain {
         f: &mut Fivemat,
         state: &TestState,
         to: &str,
+        val: &ValueRef,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
-                writeln!(f, "write_field({to}, &{}u32);", u32::MAX)?;
+                let val_idx = val.absolute_val_idx;
+                writeln!(f, "write_val({to}, {val_idx}, &{}u32);", u32::MAX)?;
             }
             WriteImpl::Assert | WriteImpl::Print => {
                 writeln!(f, r#"unreachable!("enum had unexpected variant!?")"#)?;
@@ -294,16 +294,16 @@ impl RustcToolchain {
         Ok(())
     }
 
-    pub fn write_end_function(
+    pub fn write_set_function(
         &self,
         f: &mut dyn Write,
         state: &TestState,
-        inputs: &str,
-        outputs: &str,
+        vals: &str,
+        idx: usize,
     ) -> Result<(), GenerateError> {
         match state.options.val_writer {
             WriteImpl::HarnessCallback => {
-                writeln!(f, "finished_func({inputs}, {outputs});")?;
+                writeln!(f, "set_func({vals}, {idx});")?;
             }
             WriteImpl::Print | WriteImpl::Noop | WriteImpl::Assert => {
                 // Noop


Test patch: