Variables, aliases and constants are allocated/defined/whatever at compiletime - not runtime. So when the compiler hits the DG2 subroutine it sees you want a variable/alias called AX but it can't do that for you since you allready have a variable/alias with that exact name - and since it's a compile time thing it can't re-define it at runtime.

I can of course avoid this, by introducing additional variables for each digit, like having AX, AX2, AX3, AX4, but this is just waste of memory,
No, not in this case since the names AX, AX2, AX3 etc are just aliases/pointers to already existing registers, namely PORTx.y and so on - so no wase of memory in this particular case.

You can have several aliases pointing to the same address but you can't have one alias pointing to different addresses - how's the compiler gonna know when to use which?

I suppose you could use an array containing the offset from, lets say PortA.0. The first element would be your AX, second element would be AX1 and so on. In each subroutine you then populate that array differently depending on "where" you want AX, AX1 etc to point.