I've tested some ideas with Volatile!T but there's always one remaining problem:
In C people often define a macro to describe a MMIO location: #define PORTB *((ubyte*)0x05) which can then be used like this: PORTB |= 0b1000_0000; It's not really possible to represent this in D. There are workarounds, but all have drawbacks. Naive approaches require space in the data section. Defining PORTB as an enum ubyte* is working, but then operator overloading doesn't work correctly (or the user always has to dereference manually). immutable ubyte* also doesn't work because of transitivity. So I think we do need a way to specify this: I've got an extern variable, and it's at this address. This is quite similar to pragma(mangle), so in some way it seems natural to use this: pragma(address, 0x05) extern ubyte PORTB; But does this really make sense? What makes a variable a variable? For example the GCC backends has builtin support for extern, static, const, manifest variables, but no way to specify an address for an extern variable. Is there a reason for this? (Another solution are alias expressions but that's much more invasive.) I've also implemented a small proof-of concept for this idea: Right now I simply implemented the pragma and return a pointer dereference expression from VarExp::sematic. This seems to work fine so far, but are there other ways to access a variable without a VarExp or could there be any other problems? --------- import gcc.builtins; struct Noop { ubyte _data; void opOpAssign(string op)(in ubyte rhs) nothrow { ubyte val = __builtin_volatile_load(&_data); mixin("val" ~ op ~ "= rhs;"); __builtin_volatile_store(&_data, val); } } pragma(address, 0x1000) extern Noop PORTB; void main() { auto addr = &PORTB; PORTB |= 0b1000_000; } ;; Function D main (_Dmain) ;; enabled by -tree-original { struct Noop * addr; (void) (addr = 4096B); opOpAssign (4096B, 64); return <retval> = 0; } ;; Function opOpAssign (_D4test4Noop25__T10opOpAssignVAyaa1_7cZ10opOpAssignMFNbxhZv) ;; enabled by -tree-original { ubyte val; if (this != 0) { <<< Unknown tree: void_cst >>> } else { _d_assert_msg ({.length=9, .ptr="null this"}, {.length=9, .ptr="../test.d"}, 7); } (void) (val = (ubyte) *(volatile ubyte *) &this->_data); (void) (val = val | (ubyte) rhs); (void) (*(volatile ubyte *) &this->_data = val); } (With -O1 this generates perfect ASM. Of course once we have this working there are much better ways to access the registers than simple bit manipulation)