The fact that an instance of a struct lives on the heap or the stack is not relevant at all. It's nice to know that it will live on the heap on Microsoft's implementation of the CLR, but that's about it. It's just an implementation detail.
Value types (structs) always have value semantics. This is not the same as saying that they pass-by-value, because it is relevant even when there are no method calls:
struct Data { public int a; public int b; } // Don't do this at home
var x = new Data { a = 1, b = 42 }; // x.a == 1, x.b == 42
var y = x; // x.a == 1, x.b == 42, y.a == 1, y.b == 42
y.a = 2; // x.a == 1, y.a == 2
var z = y; // x.a == 1, y.a == 2, z.a == 2
z.a = 3; // x.a == 1, y.a == 2, z.a == 3
// All variables hold a different copy of the struct
Debug.Assert(x.a == 1);
Debug.Assert(y.a == 2);
Debug.Assert(z.a == 3);
Debug.Assert(x.b == 42);
Debug.Assert(y.b == 42);
Debug.Assert(z.b == 42);
(This fact makes it generally a bad idea to have mutable structs like the one above. I used it just for demonstration purposes. Don't do it on real code. Keep your value types immutable.)
If you had a reference type, you would have a different picture:
class Data { public int a; }
var x = new Data { a = 1 }; // x.a == 1
var y = x; // x.a == 1, y.a == 1
y.a = 2; // x.a == 2, y.a == 2
var z = y; // x.a == 2, y.a == 2, z.a == 2
z.a = 3; // x.a == 3, y.a == 3, z.a == 3
// All variables hold a reference to the same object:
Debug.Assert(x.a == 3);
Debug.Assert(y.a == 3);
Debug.Assert(z.a == 3);