Guys, thanks for the feedback on this. Rob, I'm looking forward to seeing what you come up with. Being that I am exclusively into the AVR8 platform right now, my present concern with "portability" is between Arduino and vanilla AVR C environments, although I suspect I will eventually branch out into ARM when I find myself regularly banging my head against processing and memory limitations. For now, if I can come up with a way to compile code on anything from an ATtiny to an ATmega1284p with fairly minimal modifications, I'll be happy.
I did find the _sfr_addr() macro, which I think gives me the address I'm looking for, I just have to find out if it's universally safe to perform IO with _sfr_io8(addr) or if there are differences within the product line that would break that approach. Finally, I would want to have wrappers to resolve more friendly nomenclature, like "PINB" on AVR, and e.g. "D12" if ARDUINO is defined. I would prefer this all be done by the preprocessor, so the compiled code is as close to hardware as it can get. Compiler optimizations might also help resolve things like pin offsets, turning "PINB & (1 << PB5)" into "*(uint_8t *0x18) & (1 << 5)" into the cheapest assembly instructions that would achieve that I/O request. Finally, I noticed there seems to be a trend in the register addresses where the order is PINx, DDRx, PORTx on 1-byte boundaries. Again, not sure if that's "common" or "guaranteed" yet.
I want to avoid too many device-specific headers, since that duplicates a lot of work, requires that I maintain those duplicates, and makes human error exceedingly likely. If it came down to that, I think I'd rather just have the user search-and-replace the PIN/PORT/DDR registers with their preferred ones.