I added this line to the linker script in order to be able to make _SPI0 extern:
_SPI0 = 0x4002C000
I then declared a class which would have all SPI registers as members (only one is needed here to show the effect):
class SPI
    volatile uint32_t MCR;
};// __attribute__((packed));
extern SPI _SPI0;

int main(void)
  _SPI0.MCR = 0;
The generated assembler is exactly the same as when using SPI0_MCR from the teensy3 header file:
00000464 <main>:
     464:	4b01      	ldr	r3, [pc, #4]	; (46c <main+0x8>)
     466:	2000      	movs	r0, #0
     468:	6018      	str	r0, [r3, #0]
     46a:	4770      	bx	lr
     46c:	4002c000 	.word	0x4002c000
When I uncomment the packed attribute, the output changes to:
00000464 <main>:
     464:	4b02      	ldr	r3, [pc, #8]	; (470 <main+0xc>)
     466:	2000      	movs	r0, #0
     468:	681a      	ldr	r2, [r3, #0]
     46a:	6018      	str	r0, [r3, #0]
     46c:	4770      	bx	lr
     46e:	bf00      	nop
     470:	4002c000 	.word	0x4002c000
There is an extra ldr instruction (at 468) which, to my understanding, serves no purpose. Why does it do that? Adding the attribute aligned(4) fixes this (i.e. when using __attribute__((packed,aligned(4))) ). The extra nop is needed for padding so that the address constant is aligned.