unsafeCoerce just tells the typechecker to use a different type for that value. Values of all types as stored in memory have pretty much the same basic layout: constructor tag as an Int# counting upward from 0 per type, followed by a pointer for each associated value (or the actual value if it's unboxed; so an Int is stored as an 0# followed by an Int# representing its value), each of these consuming a single basic word (64 bits on a 64-bit platform). So a Bool is the same as an Int# which is 0# or 1#, and for an Either String Int you have a constructor tag 0# for Left or 1# for Right, followed by a single pointer whose associated type is determined by the constructor tag (here, either it points to a String or to an Int).
Unpacking and/or unboxing values can alter this and complicate things. See the GHC manual with respect to the UNPACK pragma and -funbox-strict-fields; you may under some circumstances need to disable optimization if you want to (ab)use unsafeCoerce.
So in short, info tables don't usually come into play at this level. But you can cause core dumps if you're not careful, especially if you do something like coercing a value from a type with 2 constructors to a type that has only 1. It is, however, safe if you ensure the runtime representation of values of both types are the same (but as above, ensuring this sometimes means compiling without optimization).