Working with ConceptClang: Binding input arguments to functions.

Here’s a basic example I’ve been trying to get to work:

explicit concept A {
typename AType;

AType h(AType a) {
return a;
}

AType f(AType a) {
AType b;
return b;
}
}

concept_map A {
typedef char AType;
}

template
requires (A)
void func(T a, AType b) {
AType x = h(b); // ← Instantiation DOES NOT WORK
AType y = f(b); // ← Instantiation WORKS
}

int main(int argc, char **argv)
{
func(10, ‘a’); // Fails with declaration for ‘x’, for passes with declaration of ‘y’.
}

It appears that during the instantiation of func(), the value for input parameter ‘b’ for ‘func()
can be looked up no problem. However, that of ‘a’ for associated function ‘h()’ cannot.
Instead, a call to clang::LocalInstantiationScope::getInstantiationOf() fails at runtime on the assertion:

(D->isInvalidDecl() && “declaration was not instantiated in this scope!”).

I have been tracing through the procedures for building, transforming, and rebuilding Call Expressions – as well as instantiating functions, noting each
execution of clang::LocalInstantiationScope::getInstantiationOf() and clang::LocalInstantiationScope::InstantiatedLocal()

Still, I can’t see what needs to be done differently for calls to h() from calls to func(). Hence…

Question:
When/how exactly are input arguments bound to functions?
Why would the current procedure fail to bind the value of ‘b’ from the context of func() to the
parameter ‘a’ of ‘h()’ ?

Thanks,

– Larisse.

Larisse Voufo

I don't have a solution to this, but here is a related code which does work using the concept clang.

What it shows is that the lookup which fails in the concept works outside it. Something in the concept context is messing it up.

John Fletcher

template <typename AType>
AType hh(AType a) {
  return a;
}

template <typename AType>
AType ff(AType a) {
  AType b = a;
  return b;
}

explicit concept A<typename T, typename AType > {

    AType h(AType a) {
        return a;
    }

    AType f(AType a) {
      AType b = 10;// = a; NOTE: the accessing of a fails here.
        return b;
    }
}

concept_map A<int, char> {
  // typedef char AType;
}

template<typename T, typename U>
requires (A<T,U>)
void func(T a,U b) {
  //U x = h(b); // <-- Instantiation DOES NOT WORK
  U y = f(b); // <-- Instantiation WORKS
}

int main(int argc, char **argv)
{
  func<int,char>(10, 'a'); // Fails with declaration for 'x', for passes
                             // with declaration of 'y'.
  int a = hh(10);
  int b = ff(20);
    return 0;
}

Hi John –

Indeed, the example you have below is supposed to work as argument binding for the functions hh() and ff() is equivalent that for func. The problem occurs when I’m trying to continue binding those arguments throughout
(inner) calls to associated types, in cases where function templates are actually constrained.

Thanks for pointing it out though. It certainly helps clarify my point.

– Larisse.

Here’s a basic example I’ve been trying to get to work:

explicit concept A {
typename AType;

AType h(AType a) {
return a;
}

AType f(AType a) {
AType b;
return b;
}
}

concept_map A {
typedef char AType;
}

template
requires (A)
void func(T a, AType b) {
AType x = h(b); // ← Instantiation DOES NOT WORK
AType y = f(b); // ← Instantiation WORKS
}

int main(int argc, char **argv)
{
func(10, ‘a’); // Fails with declaration for ‘x’, for passes with declaration of ‘y’.
}

It appears that during the instantiation of func(), the value for input parameter ‘b’ for ‘func()
can be looked up no problem. However, that of ‘a’ for associated function ‘h()’ cannot.
Instead, a call to clang::LocalInstantiationScope::getInstantiationOf() fails at runtime on the assertion:

(D->isInvalidDecl() && “declaration was not instantiated in this scope!”).

I have been tracing through the procedures for building, transforming, and rebuilding Call Expressions – as well as instantiating functions, noting each
execution of clang::LocalInstantiationScope::getInstantiationOf() and clang::LocalInstantiationScope::InstantiatedLocal()

Still, I can’t see what needs to be done differently for calls to h() from calls to func(). Hence…

Question:
When/how exactly are input arguments bound to functions?

That’s a runtime issue. All the front-end does is enforce the calling convention on both sides of the call.

Why would the current procedure fail to bind the value of ‘b’ from the context of func() to the
parameter ‘a’ of ‘h()’ ?

They are in completely different contexts. func’s b is completely disjoint from h’s a. And, in fact, they might not be the same thing, since passing ‘b’ as an argument to h() implies the creation of a temporary.

The only way I could see this issue coming up is if you’re doing some kind of inlining of A::h into func as part of instantiation. If so, I recommend against doing that: inlining will be handled by later optimization passes, and tangling the func instantiation with the A::h instantiation is going to cause more problems in the front end than it solves.

  • Doug

Here’s a basic example I’ve been trying to get to work:

explicit concept A {
typename AType;

AType h(AType a) {
return a;
}

AType f(AType a) {
AType b;
return b;
}
}

concept_map A {
typedef char AType;
}

template
requires (A)
void func(T a, AType b) {
AType x = h(b); // ← Instantiation DOES NOT WORK
AType y = f(b); // ← Instantiation WORKS
}

int main(int argc, char **argv)
{
func(10, ‘a’); // Fails with declaration for ‘x’, for passes with declaration of ‘y’.
}

It appears that during the instantiation of func(), the value for input parameter ‘b’ for ‘func()
can be looked up no problem. However, that of ‘a’ for associated function ‘h()’ cannot.
Instead, a call to clang::LocalInstantiationScope::getInstantiationOf() fails at runtime on the assertion:

(D->isInvalidDecl() && “declaration was not instantiated in this scope!”).

I have been tracing through the procedures for building, transforming, and rebuilding Call Expressions – as well as instantiating functions, noting each
execution of clang::LocalInstantiationScope::getInstantiationOf() and clang::LocalInstantiationScope::InstantiatedLocal()

Still, I can’t see what needs to be done differently for calls to h() from calls to func(). Hence…

Question:
When/how exactly are input arguments bound to functions?

That’s a runtime issue. All the front-end does is enforce the calling convention on both sides of the call.

And how does it do that? Exactly, when is func’s ‘b’ supposed to meet h’s ‘a’?
Just as… When do the main’s 10 and ‘a’ meet func’s ‘a’ and ‘b’ ?

Why would the current procedure fail to bind the value of ‘b’ from the context of func to the
parameter ‘a’ of h ?

They are in completely different contexts. func’s b is completely disjoint from h’s a. And, in fact, they might not be the same thing, since passing ‘b’ as an argument to h() implies the creation of a temporary.

The only way I could see this issue coming up is if you’re doing some kind of inlining of A::h into func as part of instantiation. If so, I recommend against doing that: inlining will be handled by later optimization passes, and tangling the func instantiation with the A::h instantiation is going to cause more problems in the front end than it solves.

Hmm… How would the call be handled if h was a global function – a specialization of some template, not associated with any concept?
I am not trying to achieve any inlining here, I just want to pass in the arguments as I would any inner function call (though thanks for the tip)…

Thanks,
– Larisse.

Dear All

It looks as though this effort is in an early stage. I don't know if anyone has done this already, but I have started to adapt the material in the <concepts> file in ConcentsGcc.

I have turned concepts into explicit concepts and auto concepts into implicit concepts, which may be the wrong thing to do. Most of it compiles, except some things which I guess have not been implemented yet.

Please let me know if this is NOT a useful idea.

I will do what I can to help, particularly on the testing end.

Best wishes

John

P.S. Doug will know that I am keen on concepts (and variadics too).

old_concepts.cpp (9.63 KB)

Here’s a basic example I’ve been trying to get to work:

explicit concept A {
typename AType;

AType h(AType a) {
return a;
}

AType f(AType a) {
AType b;
return b;
}
}

concept_map A {
typedef char AType;
}

template
requires (A)
void func(T a, AType b) {
AType x = h(b); // ← Instantiation DOES NOT WORK
AType y = f(b); // ← Instantiation WORKS
}

int main(int argc, char **argv)
{
func(10, ‘a’); // Fails with declaration for ‘x’, for passes with declaration of ‘y’.
}

It appears that during the instantiation of func(), the value for input parameter ‘b’ for ‘func()
can be looked up no problem. However, that of ‘a’ for associated function ‘h()’ cannot.
Instead, a call to clang::LocalInstantiationScope::getInstantiationOf() fails at runtime on the assertion:

(D->isInvalidDecl() && “declaration was not instantiated in this scope!”).

I have been tracing through the procedures for building, transforming, and rebuilding Call Expressions – as well as instantiating functions, noting each
execution of clang::LocalInstantiationScope::getInstantiationOf() and clang::LocalInstantiationScope::InstantiatedLocal()

Still, I can’t see what needs to be done differently for calls to h() from calls to func(). Hence…

Question:
When/how exactly are input arguments bound to functions?

That’s a runtime issue. All the front-end does is enforce the calling convention on both sides of the call.

And how does it do that? Exactly, when is func’s ‘b’ supposed to meet h’s ‘a’?
Just as… When do the main’s 10 and ‘a’ meet func’s ‘a’ and ‘b’ ?

At run time, the caller puts the call arguments into registers and on the stack, and executes a call instruction. The Clang front-end never does this; optimization passes may do it at the IR level.

Why would the current procedure fail to bind the value of ‘b’ from the context of func to the
parameter ‘a’ of h ?

They are in completely different contexts. func’s b is completely disjoint from h’s a. And, in fact, they might not be the same thing, since passing ‘b’ as an argument to h() implies the creation of a temporary.

The only way I could see this issue coming up is if you’re doing some kind of inlining of A::h into func as part of instantiation. If so, I recommend against doing that: inlining will be handled by later optimization passes, and tangling the func instantiation with the A::h instantiation is going to cause more problems in the front end than it solves.

Hmm… How would the call be handled if h was a global function – a specialization of some template, not associated with any concept?

Exactly the same here.

I am not trying to achieve any inlining here, I just want to pass in the arguments as I would any inner function call (though thanks for the tip)…

You don’t need to do any argument passing. Let Clang do that for you. All you need to do is trigger the right instantiation.

  • Doug

I have turned concepts into explicit concepts and auto concepts into implicit concepts,

This is exactly the matching that is expected from ConceptClang’s design point of view.

Most of it compiles, except some things which I guess have not been implemented yet.

Yup. We’re still implementing most of the features… With luck, they should be up soon.
Also, I’ve actually noticed some regression tests failing in non-concepts-related areas of the test suites –
More backward compatibility issues for me to revise…

I will do what I can to help, particularly on the testing end.

Very much appreciated. I will keep you updated with progress…

Thanks,
– Larisse.

Here’s a basic example I’ve been trying to get to work:

explicit concept A {
typename AType;

AType h(AType a) {
return a;
}

AType f(AType a) {
AType b;
return b;
}
}

concept_map A {
typedef char AType;
}

template
requires (A)
void func(T a, AType b) {
AType x = h(b); // ← Instantiation DOES NOT WORK
AType y = f(b); // ← Instantiation WORKS
}

int main(int argc, char **argv)
{
func(10, ‘a’); // Fails with declaration for ‘x’, for passes with declaration of ‘y’.
}

It appears that during the instantiation of func(), the value for input parameter ‘b’ for ‘func()
can be looked up no problem. However, that of ‘a’ for associated function ‘h()’ cannot.
Instead, a call to clang::LocalInstantiationScope::getInstantiationOf() fails at runtime on the assertion:

(D->isInvalidDecl() && “declaration was not instantiated in this scope!”).

I have been tracing through the procedures for building, transforming, and rebuilding Call Expressions – as well as instantiating functions, noting each
execution of clang::LocalInstantiationScope::getInstantiationOf() and clang::LocalInstantiationScope::InstantiatedLocal()

Still, I can’t see what needs to be done differently for calls to h() from calls to func(). Hence…

Question:
When/how exactly are input arguments bound to functions?

That’s a runtime issue. All the front-end does is enforce the calling convention on both sides of the call.

And how does it do that? Exactly, when is func’s ‘b’ supposed to meet h’s ‘a’?
Just as… When do the main’s 10 and ‘a’ meet func’s ‘a’ and ‘b’ ?

At run time, the caller puts the call arguments into registers and on the stack, and executes a call instruction. The Clang front-end never does this; optimization passes may do it at the IR level.

Why would the current procedure fail to bind the value of ‘b’ from the context of func to the
parameter ‘a’ of h ?

They are in completely different contexts. func’s b is completely disjoint from h’s a. And, in fact, they might not be the same thing, since passing ‘b’ as an argument to h() implies the creation of a temporary.

The only way I could see this issue coming up is if you’re doing some kind of inlining of A::h into func as part of instantiation. If so, I recommend against doing that: inlining will be handled by later optimization passes, and tangling the func instantiation with the A::h instantiation is going to cause more problems in the front end than it solves.

Hmm… How would the call be handled if h was a global function – a specialization of some template, not associated with any concept?

Exactly the same here.

I am not trying to achieve any inlining here, I just want to pass in the arguments as I would any inner function call (though thanks for the tip)…

You don’t need to do any argument passing. Let Clang do that for you. All you need to do is trigger the right instantiation.

I believe I’m doing exactly what should be done with any function call, i.e. rebuilding the call expressions et al. – after hijacking the callee pointer so it points to the appropriate concept’s associated function implementation.
Apparently, in the way that I’m currently using Clang, it is not doing a very good job at taking care of things for me, and I am not sure which “parameters” I may have overlooked.

I notice that the transformation of a call expression (via TreeTransform<>::
TransformCallExpr) transforms both the callee and the arguments. I am doing just that after redefining the callee…
Somehow, this looses the temporaries instantiation that happens for normal function calls…
Note that when I run examples where h is global, it works… But when I put h in a concept, it doesn’t.
The local instantiation scope for the instantiation of h somehow can pick up any local variable, but not it’s parameters…
and this only happens in cases where h is in a concept context.

Is there something that ActOnCallExpr does that is dependent on the calling context that I am overlooking?
I’m really not sure how to diagnose this issue anymore… :-/

A quick hack that i could do, is explicitly inject an instantiation of function parameters after/while transforming the call expression. But I’m not sure (don’t think) this is a good idea, as – like you said – Clang should automatically take care of this for me.

What do you think? I’ll keep thinking about this, but thanks for your help already.

Issue resolved.

It turns out that my mistake was in the way I was (1) copying the implementation of associated functions over from concept definitions to their concept maps, and then (2) instantiating that concept map’s implementation.
Basically, after substituting the types on the associated function, since the instantiation was treating the function itself as its own “pattern”, I should have been substituting the types on the function’s body as well somehow…
But this did not look like a very trivial move…

On the other hand, it was much easier to (1) keep records of the original implementations of those associated functions, before type substitution, and then (2) to treat those original implementations as the “patterns” needed during instantiation…

Anyway, I hope this makes sense somehow. But I did make these changes, and everything works perfectly! :slight_smile:

Thanks for the help!

– Larisse.

Larrise

Where can I get the updated version, please.

John

“Pre-Alpha 4” release has been updated to reflect the changes…
Best,
– Larisse.