www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - DMD hackers: pragma(address): Is this possible?

reply Johannes Pfau <nospam example.com> writes:
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)
Nov 25 2014
next sibling parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Johannes Pfau"  wrote in message news:m51upj$u2v$1 digitalmars.com...

 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?
Makes sense to me. I image gcc backends don't natively support this because it's equivalent to casting to a pointer and dereferencing. Do we really need it with ref return and force-inline? pragma(always_inline) ref ubyte PORTB() property { return *cast(ubyte*)0x1000; } That should also result in optimal asm, right?
Nov 25 2014
next sibling parent reply Johannes Pfau <nospam example.com> writes:
Am Wed, 26 Nov 2014 00:28:52 +1100
schrieb "Daniel Murphy" <yebbliesnospam gmail.com>:

 "Johannes Pfau"  wrote in message news:m51upj$u2v$1 digitalmars.com...
 
 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?
Makes sense to me. I image gcc backends don't natively support this because it's equivalent to casting to a pointer and dereferencing. Do we really need it with ref return and force-inline? pragma(always_inline) ref ubyte PORTB() property { return *cast(ubyte*)0x1000; } That should also result in optimal asm, right?
Good idea, this works and results in equal ASM. A minor drawback is that this emits an additional function (even with always inline), but that's a problem that also occurs in other contexts and I've got a workaround for that. Inlining in GDC right now only works across modules when templates are used. Templating the PORTB property doesn't work (PORTB()() is not an lvalue when taking the address &PORTB). So I'll probably have to implement cross-module inlining then.
Nov 25 2014
parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Johannes Pfau"  wrote in message news:m522gv$1rav$1 digitalmars.com...

 Good idea, this works and results in equal ASM. A minor drawback is
 that this emits an additional function (even with always inline), but
 that's a problem that also occurs in other contexts and I've got a
 workaround for that.
Awesome.
 Inlining in GDC right now only works across modules when templates are
 used. Templating the PORTB property doesn't work (PORTB()() is not an
 lvalue when taking the address &PORTB).
 So I'll probably have to implement cross-module inlining then.
Will it be cross-module inlined if it's an alias to a templated function instantation? module a; ref ubyte IOREG(size_t addr)() { return *cast(ubyte*)addr; } alias PORTA = IOREG!(0x10000); alias PORTB = IOREG!(0x10001); ========== module b; import a; void main() { auto x = &PORTA; PORTB |= 7; }
Nov 25 2014
parent reply Johannes Pfau <nospam example.com> writes:
Am Wed, 26 Nov 2014 01:44:02 +1100
schrieb "Daniel Murphy" <yebbliesnospam gmail.com>:

 "Johannes Pfau"  wrote in message
 news:m522gv$1rav$1 digitalmars.com...
 
 Good idea, this works and results in equal ASM. A minor drawback is
 that this emits an additional function (even with always inline),
 but that's a problem that also occurs in other contexts and I've
 got a workaround for that.
Awesome.
 Inlining in GDC right now only works across modules when templates
 are used. Templating the PORTB property doesn't work (PORTB()() is
 not an lvalue when taking the address &PORTB).
 So I'll probably have to implement cross-module inlining then.
Will it be cross-module inlined if it's an alias to a templated function instantation?
No, unfortunately not. The module where the template is instantiated needs to be the 'main' module. Or rather toObjfile must have been called on the function for backend inlining. Unfortunately this seems to be a complicated task.
Nov 25 2014
parent reply "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Johannes Pfau"  wrote in message news:m5288s$l8$1 digitalmars.com...

 No, unfortunately not. The module where the template is instantiated
 needs to be the 'main' module. Or rather toObjfile must have been
 called on the function for backend inlining. Unfortunately this seems
 to be a complicated task.
Would it make sense to always call toObjfile for always-inline template functions? That should be harmless...
Nov 25 2014
parent Iain Buclaw via Digitalmars-d <digitalmars-d puremagic.com> writes:
On 25 November 2014 at 19:08, Daniel Murphy via Digitalmars-d
<digitalmars-d puremagic.com> wrote:
 "Johannes Pfau"  wrote in message news:m5288s$l8$1 digitalmars.com...

 No, unfortunately not. The module where the template is instantiated
 needs to be the 'main' module. Or rather toObjfile must have been
 called on the function for backend inlining. Unfortunately this seems
 to be a complicated task.
Would it make sense to always call toObjfile for always-inline template functions? That should be harmless...
That depends.... There's a bit of a split-brain scenario going on (or maybe catch-42). In the current architecture of DMD, there are two states, front-end and back-end. However in GDC, there are four states, front-end (DMD), front-end (GCC), middle-end and back-end. But let's ignore the last two, they don't have anything to do with us. There's just two states we are dealing with here: Front-end (AST of the source code analysed by DMD), and back-end (AST of the code generator for compiling down to object file/assembly). As it stands, the front-end AST holds more information than the back-end AST. This is because what get sent to the back-end (via toObjFile) is done for the intention of being written to the final object/assembly code, no questions asked. For DMD, this I guess is reasonable because its back-end has limited heuristic analysis. Where as in GCC we almost an overkill amount of it, to the point were we must "force_by_abi" the output of every symbol. This was not necessarily the intention, but due the preferred method of compilation (single), and the "selective" nature when it comes to template emission under this model; the back-end just cannot be trusted to make certain (size) optimisations, as 90% of the time it turns out to be in the wrong because it never gets enough information in it's callgraph to correctly determine what should and should not be emitted (we are back to the front-end knowing more information than the back-end). What would be ideal is something inbetween. Lets call this middle-end AST. A middle-end AST is built alongside or immediately after the front-end AST. Lets say for the most likely scenario, immediately after semantic3 processing has finished, which is the best time to do such things. This middle-end AST may allow for certain optimisations or heuristic analysis to be done that cannot be done in the front-end semantic processing (think of any warnings or code re-writes that we must currently do in the back-end - no doubt come with the comments: HACK, FIXME, or BUG XXX). The crucial thing to understand though is that the middle-end AST is just a transitive layer. Nothing done here is guaranteed to be emitted to the resultant object file unless it lands at toObjFile. At this point, the middle intermediate representation gets lowered/send it down for finalising the compilation. But, I guess we now have visitors for this sort of thing, so.... tl;dr foreach(m; module) { m.semantic(); ToBackendVisitor::accept(m); // pre-build back-end AST (doesn't exist) } foreach(m; module) { if (m in output_modules) m.toObjFile(); // Now send to object file } -- Iain
Nov 26 2014
prev sibling parent reply Johannes Pfau <nospam example.com> writes:
Am Wed, 26 Nov 2014 00:28:52 +1100
schrieb "Daniel Murphy" <yebbliesnospam gmail.com>:

 "Johannes Pfau"  wrote in message news:m51upj$u2v$1 digitalmars.com...
 
 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?
Makes sense to me. I image gcc backends don't natively support this because it's equivalent to casting to a pointer and dereferencing. Do we really need it with ref return and force-inline? pragma(always_inline) ref ubyte PORTB() property { return *cast(ubyte*)0x1000; } That should also result in optimal asm, right?
Is taking addresses on properties still undefined? Or how exactly is it defined? Anyway, &PORTB returns the address of the PORTB function which is a small annoyance.
Nov 25 2014
parent "Daniel Murphy" <yebbliesnospam gmail.com> writes:
"Johannes Pfau"  wrote in message news:m52aq3$dla$1 digitalmars.com...

 Is taking addresses on properties still undefined? Or how exactly is it
 defined? Anyway, &PORTB returns the address of the PORTB function
 which is a small annoyance.
Urrgh I forgot about that. Hopefully property will be fixed one of these years.
Nov 25 2014
prev sibling parent "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Tuesday, 25 November 2014 at 13:04:51 UTC, Johannes Pfau wrote:
 In C people often define a macro to describe a MMIO location:
 #define PORTB *((ubyte*)0x05)
FWIW, Turbo Pascal had a language feature for this: var Screen: array[0..8000-1] of Word absolute $B800:0000;
Nov 27 2014