The issue seems to be that CPython is optimizing the locals access for the exec_code_object_and_return_x by using indices into the array, instead of the normal lookup by name (which, by incident, is what the compiled code uses).
If I understand http://bugs.python.org/issue4831 correctly, CPython 2.7 should not be optimizing the locals access in this way with an exec statement present.
The interesting case is the second one, in which the compiled codeobj uses DELETE_NAME, which doesn't throw an error (if "del x" was in a function, it would use DELETE_FAST, which would throw an UnboundLocalError if it was used before x was assigned,) and the function then uses LOAD_FAST, which references the function argument directly, and so retrieves the value for return.
By compiling and injecting bytecode like this, the program ends up using two different methods of variable access, which seem to sidestep each other completely.
It turns out that this is not actually as surprising as I had first thought -- del unbinds a variable from the (local) scope. Since exec results in a new Python frame, it is in that frame's scope that x is deleted, not in the outer frame (of the function exec_code_object_and_return_x).
I'm in the process of blogging about this and other quirks of exec -- I'll post back here once that's up.
At least for Python 2.7, you would expect the code run with exec to share the locals and globals with the environment where you called exec from. Thats why the x = x + 1 works, after all.
This is because all integers between -5 and 256 in CPython are "cached", so they all have the same identity (hence the True return value of the `is` of the second comparison -- it's between the range).