tomago is correct, your problem is caused by signextension.
At lines 6 and 7 you have what you expect:
a is 0, and
b is 0x8000.
However, if perchance your implementation uses two'scomplement encoding for signed integers, then
b is considered to be a negative value.
Let's split line 10 into several lines so we can look at each term:
 uint32_t xa, xb;

...

xa = (uint32_t)a << 16;

xb = (uint32_t)b;

x = xa  xb;
Here
xa is 0, but
xb is 0xFFFF8000 because the sign bit is extended to the left.
By the way, are you sure the expression for
xa is correct? It is if the cast has a higher precedence than the shiftleft. Does it? You don't need to remember the precedence table if you use parentheses.
You can solve the signextension issue if your computations are always performed on unsigned values.
 uint32_t x = 0x00008000uL;

uint16_t ua, ub;

int16_t = a, b;


//Split

ua = (uint16_t) (x >> 16);

ub = (uint16_t) (x & 0x0000FFFFuL);

a = (int16_t)ua;

b = (int16_t)ub;


//Reconstruction

ua = (uint16_t)a;

ub = (uint16_t)b;

x = (((uint32_t)ua) << 16)  ((uint32_t)ub);
Notice also the use of the
uL suffix.