If architectures are similar enough, than the different shading languages and assembly languages boil down a front-end for a common backend optimizer. There is the possibility of two high level languages requiring different intermediate representations or an intermediate representation not being semantically powerful enough to express the underlying HW in a way that is easy to optimize, but in general, compilers can share alot of common code.
Take GCC for example. It actually has frontends to compile C, C++, Objective-C, Fortran 77, ADA, Pascal, Java, CAML, and a bunch of others. On top of that, it has backends to target almost every CPU and virtual machine format ever made. Most of this is shared code.
Of course, GCC by striving to be so portable and general, loses against more vertically integrated compilers, but in general, it does reasonably well on most architectures.
Many compilers are being written today using code-generator-generators (in addition to compiler-compilers like YACC), which do the dirty work of implementing instruction selection and scheduling, using so-called Bottom-Up Rewrite-Systems or Bottom-up-rewrite-grammars. These allow you to target new architectures by altering a small config file. The config file specifies the costs of various combinations of operations, so that the compiler can choose the cheapest sequence.