February 23, 2005
<brad@domain.invalid> wrote in message news:cvghv0$1u9s$1@digitaldaemon.com...
> Ben Hinkle wrote:
>> <brad@domain.invalid> wrote in message news:cvg7sd$1d4k$1@digitaldaemon.com...
>>
>>>I'm just looking at a couple of projects, and wondering how I can convert them to D.  The point of these macros to to translate a human readable value to something that is hardware specific.
>>
>> [snip]
>>
>>>const uint SOME_COMMAND = flip (0xE0);
>>>
>>>Where SOME_COMMAND is evalutated once at runtime, before the main function gets called.
>>
>> [snip]
>>
>> Does SOME_COMMAND really have to be const? Can it be an inlined function call instead? I'm thinking something like
>>
>> uint flip(uint x){ compute and return flipped x }
>> uint SOME_COMMAND(){ return flip(0xE0); }
>>
>> Then if you compile with -inline -O the call to SOME_COMMAND() and flip()
>> should be inlined and the constant folding logic in the optimizer should
>> evaluate the resulting expression at compile time. The down-sides of this
>> approach are
>> 1) assumes inlining and constant folding are enabled
>> 2) you can't take the address of SOME_COMMAND and get a uint*
>> 3) you have to write SOME_COMMAND() in code instead of SOME_COMMAND
>>
>> I think it's worth a shot, though.
>> -Ben
> That is probably a good choice.  It doesn't need to be a constant (in my case), though it is a little more code than in the C case.  It does also make assumptions about the inliner/optimiser - but that is easily verified with an assembler check.  I guess I was hoping that I would be able to control constant munging in D just as well as I can in C with the preprocessor.  I mean, with the C code I know that it will fold down to just another constant - I'll need to check the assembler output with the D method :)
>
> Brad

Well, technically all that the C version does is the inlining. The constant folding is still the same as D.


February 23, 2005
> 
> 
> Well, technically all that the C version does is the inlining. The constant folding is still the same as D. 
> 
> 

True, but would it actually work like that with plain functions?  For example, the optimiser would need to be clever enough to realise that each call to the function that has a constant input should be evaluated at compile time, and then just that constant injected into the code. And for non-const inputs the actual function code gets called.  What I think would happen is this

int invert (int i) { return ~i; };
int myConst () { return invert(0xF0); }

void code ()
{
 ...
 someFunc(myConst)
...
}

I think the optimiser would turn this into (with inlining, and constant folding)

void code ()
{
 ...
 someFunc ( ~0xF0 );
..
}
ie, the call will be inlined, but does the optimiser then further reduce that inlined call to the final optimisation of
 someFunc (0x0F) ?

Doing it with templates (as discussed in this thread) would do it properly.  I don't know enough about how the optimiser works to see if both the call will be inlined, and the constant folded.  And if I don't know the answer well enough, my first instinct (when time/space is critical) is NOT to trust the compiler to do it right :)

Brad
February 23, 2005
> Is there a reason why D the following couldn't be valid D?
> 
> static int foo(int i) { return i/2; };
> const int myVar = foo(10);

I think the issue is that foo(10) may not always return the same thing in the general case, so you'd need to do code analysis to see that it does. Requiring code analysis in the spec (meaning every compiler has to do it) is not a good thing.

The meta-programming technique (those recursive templates) is somewhat different and doesn't require code analysis (except evaluating constant expressions, but that is easy). Basically, by instantiating (sp?) those templates, a function call is simulated and its "return value" can be used..


xs0
February 23, 2005
On Wed, 23 Feb 2005 13:20:51 +1300, brad@domain.invalid wrote:

>> It might "feel a little ugly" to you, but that is exactly what module constructors are for ... "SOME_COMMAND is evaluated once at runtime, before the main function gets called".
>> 
>> In fact, I'd even make the flip() function a nested function within the module constructor if it's not needed elsewhere in the program.
>> 
> 
> The only remaining difference is that I can't assign a value to a const
> variable with the static module constructor method.
> I'm not particularly concerned, just found a little corner that I think
> C does better than D, and I'd like D to be better than C everywhere :)
> 
> Is there a reason why D the following couldn't be valid D?
> 
> static int foo(int i) { return i/2; };
> const int myVar = foo(10);
> 

Oops! I forgot about the need for a 'const'.

I've occasionally thought that a write-once type of variable would be a useful addition to a language. I other words, once an assignment to such a variable has been done, all other attempts at assigning something to it would be a run-time error. It could be used for things whose value was not knowable at compile time, but during run time, it should behave as if it was a const.


-- 
Derek
Melbourne, Australia
23/02/2005 12:34:28 PM
February 23, 2005
<brad@domain.invalid> wrote in message news:cvgm9s$25ab$1@digitaldaemon.com...
>
>>
>>
>> Well, technically all that the C version does is the inlining. The constant folding is still the same as D.
>
> True, but would it actually work like that with plain functions?  For example, the optimiser would need to be clever enough to realise that each call to the function that has a constant input should be evaluated at compile time, and then just that constant injected into the code. And for non-const inputs the actual function code gets called.  What I think would happen is this
>
> int invert (int i) { return ~i; };
> int myConst () { return invert(0xF0); }
>
> void code ()
> {
>  ...
>  someFunc(myConst)
> ...
> }
>
> I think the optimiser would turn this into (with inlining, and constant folding)
>
> void code ()
> {
>  ...
>  someFunc ( ~0xF0 );
> ..
> }
> ie, the call will be inlined, but does the optimiser then further reduce
> that inlined call to the final optimisation of
>  someFunc (0x0F) ?

It's the same as with C's macros. Let me illustrate. The C code

  #define invert(x) (~x)
  #define myConst (invert(0xF0))
  ...
  someFunc( myConst )

gets expanded by the preprocessor to
  someFunc( (~(0xF0)) )
and then the compiler's constant folding takes it from there. In D the only
difference is that there is no preprocessor to inline the "macros" - the
compiler does it instead. After inlining the D compiler ends up with the
exact same expression tree as the C version and from then on it is the same
as C.

> Doing it with templates (as discussed in this thread) would do it properly.  I don't know enough about how the optimiser works to see if both the call will be inlined, and the constant folded.  And if I don't know the answer well enough, my first instinct (when time/space is critical) is NOT to trust the compiler to do it right :)
>
> Brad

That's ok - trusting the compiler can be risky. Though if dmd's inlining and
constant folding can't produce the same result in your case as a C compiler
would then Walter needs to spend some time and fix that.
See also http://www.digitalmars.com/d/htomodule.html the section about
macros and http://www.digitalmars.com/d/pretod.html the section about
macros.
Inlining is your friend!


February 23, 2005
> 
> It's the same as with C's macros. Let me illustrate. The C code
> 
>   #define invert(x) (~x)
>   #define myConst (invert(0xF0))
>   ...
>   someFunc( myConst )
> 
> gets expanded by the preprocessor to
>   someFunc( (~(0xF0)) )
> and then the compiler's constant folding takes it from there. In D the only difference is that there is no preprocessor to inline the "macros" - the compiler does it instead. After inlining the D compiler ends up with the exact same expression tree as the C version and from then on it is the same as C.
<snip>
> That's ok - trusting the compiler can be risky. Though if dmd's inlining and constant folding can't produce the same result in your case as a C compiler would then Walter needs to spend some time and fix that.
> See also http://www.digitalmars.com/d/htomodule.html the section about macros and http://www.digitalmars.com/d/pretod.html the section about macros.
> Inlining is your friend! 
> 

That's good to hear, and it is what I expected.  I had thought that what might happen is
1) Constant folding occurs (ie, 0xF0 - this is as reduced as it can get)
2) Inlining occurs, the ~ operation still has to happen on 0xF0.
But if constant folding occurs after inlining, then everything is OK. If I am feeling particularly bored I might need to look at the assembler output.

I think the template method is closer to what I am wanting.

Brad
February 23, 2005

brad@domain.invalid wrote:
> I'm just looking at a couple of projects, and wondering how I can convert them to D.  The point of these macros to to translate a human readable value to something that is hardware specific.  The details aren't too specific, but the macros convert from one constant form to another, here is the C code.

That's a bit like the following C++, from src/dmd/parse.c


         switch (token.value)
         {
#define X(tok,ector) \
             case tok:  nextToken(); e2 = parseAssignExp(); \
                        e = new ector(loc,e,e2); continue;

             X(TOKassign,    AssignExp);
             X(TOKaddass,    AddAssignExp);
             X(TOKminass,    MinAssignExp);
             X(TOKmulass,    MulAssignExp);
             X(TOKdivass,    DivAssignExp);
             X(TOKmodass,    ModAssignExp);
             X(TOKandass,    AndAssignExp);
             X(TOKorass,     OrAssignExp);
             X(TOKxorass,    XorAssignExp);
             X(TOKshlass,    ShlAssignExp);
             X(TOKshrass,    ShrAssignExp);
             X(TOKushrass,   UshrAssignExp);
             X(TOKcatass,    CatAssignExp);

#undef X
             default:
                 break;
         }

I've found some ways to rewrite this in D, but none of them "look nice".


February 23, 2005
brad beveridge wrote:
> 
>> That's ok - trusting the compiler can be risky. Though if dmd's inlining and constant folding can't produce the same result in your case as a C compiler would then Walter needs to spend some time and fix that.
>> See also http://www.digitalmars.com/d/htomodule.html the section about macros and http://www.digitalmars.com/d/pretod.html the section about macros.
>> Inlining is your friend!
> 
> 
> That's good to hear, and it is what I expected.  I had thought that what might happen is
> 1) Constant folding occurs (ie, 0xF0 - this is as reduced as it can get)
> 2) Inlining occurs, the ~ operation still has to happen on 0xF0.
> But if constant folding occurs after inlining, then everything is OK. If I am feeling particularly bored I might need to look at the assembler output.
> 
> I think the template method is closer to what I am wanting.
> 
> Brad
As much as replying to my own post is poor form, here I am doing it. The following code illustrates both methods
1) Inlining a function call of a constant
2) Generating a constant with a template
The bad news is that you were wrong Ben :)
(1) doesn't give optimal results
(2) does
D doesn't inline & then fold constants.  Which makes sense - I don't know much about compilers/optimisers, but I expect that an optimiser would need to be very smart to decide "hey, this function has constant inputs, maybe I should evaluate it at compile time and see if I can give it a constant output"

Anyhow, here is some sample code.  A little grovelling through the assembler output shows what is going on.

Brad

import std.stdio;

int flip(int x)
{
    return ( ((x & 0x80) >> 7) |
                          ((x & 0x40) >> 5) |
                          ((x & 0x20) >> 3) |
                          ((x & 0x10) >> 1) |
                          ((x & 0x08) << 1) |
                          ((x & 0x04) << 3) |
                          ((x & 0x02) << 5) |
                          ((x & 0x01) << 7) );
}

template tflip(int x)
{
    const int val = ( ((x & 0x80) >> 7) |
                          ((x & 0x40) >> 5) |
                          ((x & 0x20) >> 3) |
                          ((x & 0x10) >> 1) |
                          ((x & 0x08) << 1) |
                          ((x & 0x04) << 3) |
                          ((x & 0x02) << 5) |
                          ((x & 0x01) << 7) );

}

void main()
{
    // comment these lines in/out to show that constant folding happens
    // before inlining.

    // sub-optimal behaviour, the flip function is called and 0xFE
    // is bit flipped at runtime
    // note, you cannot have this int "const"
    //int i = flip(0xFE);

    // optimal method - the template is evaluated at compile time
    // and i evaluates to 0x7F, as is correct
    //const int i = tflip!(0xFE).val;
    writef("%d\n", i);
}
February 23, 2005
"brad beveridge" <brad@nowhere.com> wrote in message news:cvhc9r$1vm$1@digitaldaemon.com...
> brad beveridge wrote:
>>
>>> That's ok - trusting the compiler can be risky. Though if dmd's inlining
>>> and constant folding can't produce the same result in your case as a C
>>> compiler would then Walter needs to spend some time and fix that.
>>> See also http://www.digitalmars.com/d/htomodule.html the section about
>>> macros and http://www.digitalmars.com/d/pretod.html the section about
>>> macros.
>>> Inlining is your friend!
>>
>>
>> That's good to hear, and it is what I expected.  I had thought that what
>> might happen is
>> 1) Constant folding occurs (ie, 0xF0 - this is as reduced as it can get)
>> 2) Inlining occurs, the ~ operation still has to happen on 0xF0.
>> But if constant folding occurs after inlining, then everything is OK. If
>> I am feeling particularly bored I might need to look at the assembler
>> output.
>>
>> I think the template method is closer to what I am wanting.
>>
>> Brad
> As much as replying to my own post is poor form, here I am doing it. The
> following code illustrates both methods
> 1) Inlining a function call of a constant
> 2) Generating a constant with a template
> The bad news is that you were wrong Ben :)
> (1) doesn't give optimal results
> (2) does
> D doesn't inline & then fold constants.  Which makes sense - I don't know
> much about compilers/optimisers, but I expect that an optimiser would need
> to be very smart to decide "hey, this function has constant inputs, maybe
> I should evaluate it at compile time and see if I can give it a constant
> output"
>
> Anyhow, here is some sample code.  A little grovelling through the assembler output shows what is going on.
>
> Brad
>
> import std.stdio;
>
> int flip(int x)
> {
>     return ( ((x & 0x80) >> 7) |
>                           ((x & 0x40) >> 5) |
>                           ((x & 0x20) >> 3) |
>                           ((x & 0x10) >> 1) |
>                           ((x & 0x08) << 1) |
>                           ((x & 0x04) << 3) |
>                           ((x & 0x02) << 5) |
>                           ((x & 0x01) << 7) );
> }
>
> template tflip(int x)
> {
>     const int val = ( ((x & 0x80) >> 7) |
>                           ((x & 0x40) >> 5) |
>                           ((x & 0x20) >> 3) |
>                           ((x & 0x10) >> 1) |
>                           ((x & 0x08) << 1) |
>                           ((x & 0x04) << 3) |
>                           ((x & 0x02) << 5) |
>                           ((x & 0x01) << 7) );
>
> }
>
> void main()
> {
>     // comment these lines in/out to show that constant folding happens
>     // before inlining.
>
>     // sub-optimal behaviour, the flip function is called and 0xFE
>     // is bit flipped at runtime
>     // note, you cannot have this int "const"
>     //int i = flip(0xFE);
>
>     // optimal method - the template is evaluated at compile time
>     // and i evaluates to 0x7F, as is correct
>     //const int i = tflip!(0xFE).val;
>     writef("%d\n", i);
> }

I had forgotten about this bug where initializers aren't inlined. Everything
works fine if you replace
     int i = flip(0xFE);
with
     int i;
     i = flip(0xFE);
or if you hadn't used a variable at all and instead just wrote
writef("%d\n", flip(0xFE));

Walter, is that bug fixable? It's very natural to want to put inlinable expressions in initializers.

-Ben


February 23, 2005
> Though I think the syntax is on the clunky side :)

The better way:

template Foo(int n)
{
     const int Foo = n - 1;
}

const int test = Foo!(10);