Thread overview
Initialising Context Pointer during Semantic Analysis
Nov 25, 2022
Teodor Dutu
Nov 25, 2022
Teodor Dutu
Nov 26, 2022
Johan
Nov 26, 2022
Iain Buclaw
Nov 26, 2022
Timon Gehr
Nov 28, 2022
Teodor Dutu
Nov 28, 2022
Teodor Dutu
November 25, 2022

Hi,

For SAoC 2022, I want to translate DRuntime hooks to templates. I am working on _d_newitem{U,T,iT}. The compiler uses them to lower new S(args) expressions, where S is a struct.

To instantiate the new template hooks, the lowering has to be moved from the glue layer to the semantic analysis. However, when the constructed struct is nested, the lowering logic also needs to copy the struct's context pointer(s). This is currently handled by the glue layer in e2ir.d. Now the same logic needs to be replicated in the semantic analysis, where there is no machinery to find the right frame pointer, such as getEthis() in toir.d.

After discussing with my mentors, we have come up with 2 possible approaches:

  1. Add a new AST node for copying context pointers to expression.d: ContextPointerExp. The new lowering of new S(args) in the semantic phase will insert a ContextPointerExp node and then the glue layer will visit it and copy the correct context pointer to the newly created struct. This approach is efficient from a runtime perspective, as it doesn't add any unnecessary computation, but has the disadvantage of requiring changes beyond the frontend. Thus it won't be transparent for LDC and GDC, who will have to handle ContextPointerExp in their own glue layers.

  2. Change the lowering to something like this:

S* s = new S();
// lowering:
S* s = (S tmp, _d_newitem(tmp));

This way, tmp's context pointer will be set by default and _d_newitem will be able to simply copy it from tmp to s in a way similar to copyEmplace(). This solution is not without faults, though, as it requires creating and initialising tmp only to get its context pointer.

void-initialising tmp from the snippet above is not a solution, as that also leaves the context pointer uninitialised. The code below either seg faults, or prints a random value for s.y because the context pointer of s is null:

void main()
{
    int x = 3;

    struct S
    {
        int y;
        void bar()
        {
            y = x;
        }
    }

    S s = void;
    s.bar();

    writeln(s);
}

Do you see a better approach than 2? How should we handle context pointers in the semantic phase?

Thanks,
Teo

November 25, 2022

On Friday, 25 November 2022 at 16:10:36 UTC, Teodor Dutu wrote:

>

void-initialising tmp from the snippet above is not a solution, as that also leaves the context pointer uninitialised. The code below either seg faults, or prints a random value for s.y because the context pointer of s is null:

void main()
{
    int x = 3;

    struct S
    {
        int y;
        void bar()
        {
            y = x;
        }
    }

    S s = void;
    s.bar();

    writeln(s);
}

Also, is it the expected behaviour to leave the context pointer uninitalised as well when using S s = void or is it a bug? It seems odd to not set the context pointer as this makes S s = void risky to use for nested structs.

November 26, 2022

On Friday, 25 November 2022 at 16:10:36 UTC, Teodor Dutu wrote:

>

Hi,

For SAoC 2022, I want to translate DRuntime hooks to templates. I am working on _d_newitem{U,T,iT}. The compiler uses them to lower new S(args) expressions, where S is a struct.

To instantiate the new template hooks, the lowering has to be moved from the glue layer to the semantic analysis. However, when the constructed struct is nested, the lowering logic also needs to copy the struct's context pointer(s). This is currently handled by the glue layer in e2ir.d. Now the same logic needs to be replicated in the semantic analysis, where there is no machinery to find the right frame pointer, such as getEthis() in toir.d.

After discussing with my mentors, we have come up with 2 possible approaches:

  1. Add a new AST node for copying context pointers to expression.d: ContextPointerExp. The new lowering of new S(args) in the semantic phase will insert a ContextPointerExp node and then the glue layer will visit it and copy the correct context pointer to the newly created struct. This approach is efficient from a runtime perspective, as it doesn't add any unnecessary computation, but has the disadvantage of requiring changes beyond the frontend. Thus it won't be transparent for LDC and GDC, who will have to handle ContextPointerExp in their own glue layers.

This is not a deal breaker, as long as it is clearly communicated. So add a special section to the release notes for frontend-dependent projects what has changed / what was added. Adding AST nodes impacts more than just GDC and LDC.

Why not extend the information attached to a NewExpression node?
AST rewriting makes it much harder (or impossible) to generate good user diagnostics and debug information.

-Johan

November 26, 2022

On Saturday, 26 November 2022 at 11:17:45 UTC, Johan wrote:

>

Why not extend the information attached to a NewExpression node?
AST rewriting makes it much harder (or impossible) to generate good user diagnostics and debug information.

This.

We also lose optimization opportunities with every early lowering (LDC gc2stack, GDC call/argument/return flags, assume hints, etc)

November 26, 2022
On 11/25/22 17:10, Teodor Dutu wrote:
> 
> After discussing with my mentors, we have come up with 2 possible approaches:

Probably the right approach is to lower the call to a local function, e.g., with local template instantiation.

This is a somewhat hacky way to achieve that without changing the compiler frontend:

auto make(T,alias dummy)(){
    return T(); // can do your emplace magic here
}

auto foo(){
    int x=123;
    struct S{
        int bar(){ return x; }
    }
    void[0] context;
    return make!(S,context)();
}


void main(){
    import std.stdio;
    writeln(foo().bar()); // 123
}


TBH, I think local template instantiation should work even without that dummy parameter and with an arbitrary number of contexts.


General remark: I think the whole point of this project is to get rid of compiler magic, so instead of introducing compiler magic and magic-UB-that-works-anyway, maybe propose general language features to cover the missing use cases. Not only compiler developers may want to hook into builtin behavior.
November 28, 2022

On Saturday, 26 November 2022 at 18:12:44 UTC, Timon Gehr wrote:

>

On 11/25/22 17:10, Teodor Dutu wrote:

>

After discussing with my mentors, we have come up with 2 possible approaches:

Probably the right approach is to lower the call to a local function, e.g., with local template instantiation.

This is a somewhat hacky way to achieve that without changing the compiler frontend:

auto make(T,alias dummy)(){
return T(); // can do your emplace magic here
}

auto foo(){
int x=123;
struct S{
int bar(){ return x; }
}
void[0] context;
return make!(S,context)();
}

void main(){
import std.stdio;
writeln(foo().bar()); // 123
}

I don't understand why passing context to make() is better than passing it to _d_newitemT() directly. I would still use context to copy the context pointer, so wouldn't it be the same?

November 28, 2022

On Monday, 28 November 2022 at 17:25:06 UTC, Teodor Dutu wrote:

>

I don't understand why passing context to make() is better than passing it to _d_newitemT() directly. I would still use context to copy the context pointer, so wouldn't it be the same?

In addition, in my case, isn't the alias dummy parameter the same as a const ref T dummy parameter? Does the alias somehow allow me to get the context pointer more elegantly than copying it from dummy? If not, then I would still need to create dummy, pass it as a ref to a template (either the local one or the hook itself) and then copy the context pointer from it.