Opened 4 months ago

#272 new defect

Compiler crash when referencing generic member of generic struct

Reported by: mlbrooks Owned by:
Priority: major Component: cfa-cc
Version: 1.0 Keywords:
Cc:

Description

Possible overlap with #166, but the specific failure cases and symptom here are different. Some of the test-case driving presented here might help drive a similar breadth of cases for working #166.

Headline case:

forall( E & ) struct inner { E * item; };
forall( E & ) struct outer { inner(E) wrapped; };

forall(E &)
void f( outer(E) & out, E * elem ) { 
    out.wrapped.item = elem;
}

ACUTAL: Assertion failure in Type::genericSubstitution() during new-to-old AST conversion.

EXPECTED: Compiles successfully. If run with the harness below, prints -1.000000 3.140000 1.414000.

The breadth of cases that follows illustrates when the issue does (not) happen. I don't expect you to read the preprocessor acrobatics, but I hope you can understand the -E output of each concrete case. The headline case above is a small formatting edit of the -E output of the no-defines case, whose explicit coordinates are -DRUNCASE=1 -DTYPECASE_INNER_GENER -DTYPECASE_OUTER_GENER -DTYPECASE_WRAPPER_TRAD.

demo.cfa:

#if 1 < ((defined TYPECASE_INNER_GENER) + (defined TYPECASE_INNER_CONCR))
#error multiple TYPECASE_INNER_X
#endif

#if 1 < ((defined TYPECASE_OUTER_GENER) + (defined TYPECASE_OUTER_CONCR))
#error multiple TYPECASE_OUTER_X
#endif

#if 1 < ((defined TYPECASE_WRAPPER_TRAD) + (defined TYPECASE_WRAPPER_PLN9))
#error multiple TYPECASE_WRAPPER_X
#endif


#if (!defined TYPECASE_INNER_CONCR) && (!defined TYPECASE_INNER_GENER)
#define TYPECASE_INNER_GENER
#endif

#if (!defined TYPECASE_OUTER_CONCR) && (!defined TYPECASE_OUTER_GENER)
#define TYPECASE_OUTER_GENER
#endif

#if (!defined TYPECASE_WRAPPER_PLN9) && (!defined TYPECASE_WRAPPER_TRAD)
#define TYPECASE_WRAPPER_TRAD
#endif



                        #define DEFTY_INNER_FRAG(T) struct inner { T * item; };
#if defined TYPECASE_INNER_GENER
                        #define DEFTY_INNER  forall( E & ) DEFTY_INNER_FRAG(E)
                        #define TY_INNER(T)  inner(T)
#else
                        #define DEFTY_INNER  DEFTY_INNER_FRAG(float)
                        #define TY_INNER(T)  inner
#endif

#if defined TYPECASE_WRAPPER_TRAD
                        #define DEFFLD(ty)   ty wrapped;
                        #define FLD(obj)     obj.wrapped
#else
                        #define DEFFLD(ty)   inline ty;
                        #define FLD(obj)     obj
#endif

                        #define DEFTY_OUTER_FRAG(T) struct outer { DEFFLD(TY_INNER(T)) };
#if defined TYPECASE_OUTER_GENER
                        #define DEFTY_OUTER  forall( E & ) DEFTY_OUTER_FRAG(E)
                        #define TY_OUTER(T)  outer(T)
                        #define DEFFUNC(...) forall(E &) { __VA_ARGS__ }
#else
                        #define DEFTY_OUTER  DEFTY_OUTER_FRAG(float)
                        #define TY_OUTER(T)  outer
                        #define DEFFUNC(...) __VA_ARGS__
#endif
                        #define GENTY_OUTER  TY_OUTER(E)

#if (defined TYPECASE_INNER_GENER) && (defined TYPECASE_OUTER_GENER)
                        #define ELEM E
#else
                        #define ELEM float
#endif



DEFTY_INNER
DEFTY_OUTER


                                 #if   RUNCASE==4 || RUNCASE==5 || RUNCASE==6 || RUNCASE==7
DEFFUNC (
    void rval_helper( ELEM * ) {
        // this helper is a nop
        // SUT action to be done as follow-up step
    }
    void lval_helper( ELEM * & tgt, ELEM * val ) {
        // this helper does the SUT action
        tgt = val;
    }
    void mockStep( ELEM * & tgt, ELEM * val ) {
    }
)
// follow-up step for no-op variations
// applies workaround B (assumes it succeeds)
// (cases that use the mock action only illustrate wheteher the compiler claims success)
#define MOCK_SUT_ACTION ((TY_INNER(ELEM)&)(FLD(out))).item = elem; 

                                 #endif

DEFFUNC (
    void f( GENTY_OUTER & out, ELEM * elem ) {
                                 #if   RUNCASE==1  // headline, maximally broken
        FLD(out).item = elem;
                                 #elif RUNCASE==2  // workaround A, successful sometimes
        (*&(FLD(out))).item = elem;
                                 #elif RUNCASE==3  // workaround B, successful always
        ((TY_INNER(ELEM)&)(FLD(out))).item = elem;
                                 #elif RUNCASE==4  // variation as top-level expr, not broken
        FLD(out).item;
        MOCK_SUT_ACTION
                                 #elif RUNCASE==5  // variation as simple r-value, not broken
        rval_helper( FLD(out).item );
        MOCK_SUT_ACTION
                                 #elif RUNCASE==6  // variation as simple l-value, blocked by #166
        lval_helper( FLD(out).item, elem );
                                 #elif RUNCASE==7  // variation as hacked l-value, maximally broken
        // here, *& is a #166-workaround
        // looks like workaround A: coincidence?
        lval_helper( *& FLD(out).item, elem );
                                 #else
                                 #error bad RUNCASE
                                 #endif
    }
)

// Gives a demonstration of "compiler does right thing," as more than "compiler claims success."
// Except for runcases 4,5.
// While the innermost datum being a pointer adds complexity at the harness level, it removes a
// confounding factor from the core repro: the failure happens with `forall(E&)`.  Switching the
// core datum to a non-pointer would make these declarations `forall(E)`, which would add boxing
// to the polymorphic calls.
int main() {

    TY_OUTER(float) obj;

    // `obj` content test point: because "proper" access via fields is SUT,
    // circumvent that for the test harness
    float * & objContTP = * (float **) & obj;

    // state 1, via TP: obj points to oldbox
    float oldbox = -1.0;
    objContTP = &oldbox;
    printf("%f ", *objContTP);

    // state 2, via SUT: obj points to newbox
    float newbox = 3.14;
    f(obj, &newbox);
    printf("%f ", *objContTP);

    // mutation in state 2: show state 2 has the pointing expected
    newbox = 1.414;
    printf("%f\n", *objContTP);
}

run.sh:

dotest() {
#   (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER -E           )
    (set -x; $cfa demo.cfa $RUNCASE $TYPECASE_INNER $TYPECASE_OUTER $TYPECASE_WRAPPER    && ./a.out)
}

# Failures that are success-adjacent, and successes that are failure-adjacent, are driven here.

for RUNCASE in '-DRUNCASE=1'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
do
    dotest
done
done
done
done

for RUNCASE in '-DRUNCASE=2' '-DRUNCASE=3'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
do
    dotest
done
done
done
done

for RUNCASE in '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7'
do
for TYPECASE_INNER in '-DTYPECASE_INNER_GENER'
do
for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER'
do
for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD'
do
    dotest
done
done
done
done


# All combinations are driven here
# "All combinations pass" is a fine condition for statisfaction.

# for RUNCASE in '-DRUNCASE=1' '-DRUNCASE=2' '-DRUNCASE=3' '-DRUNCASE=4' '-DRUNCASE=5' '-DRUNCASE=6' '-DRUNCASE=7'
# do
# for TYPECASE_INNER in '-DTYPECASE_INNER_GENER' '-DTYPECASE_INNER_CONCR'
# do
# for TYPECASE_OUTER in '-DTYPECASE_OUTER_GENER' '-DTYPECASE_OUTER_CONCR'
# do
# for TYPECASE_WRAPPER in '-DTYPECASE_WRAPPER_TRAD' '-DTYPECASE_WRAPPER_PLN9'
# do
#     dotest
# done
# done
# done
# done

Invoking ./run.sh gives

ACUTAL: mix of crashes, as in headline-actual, and successful runs, as in headline-expected, according to the case summary following, except with RUNCASE=6 being an occurrence of #166

EXPECTED: always a successful run, as in headline-expected, except with RUNCASE=6 being either a successful run or an occurrence of #166

Summary of the cases and actual outcomes (to be read along with the -E dump):

  • RUNCASE=1 is the vanilla version. Running it across varied generic/concrete options for TYPECASE_INNER and TYPECASE_OUTER shows that it only happens when both levels are generic. Running it across varied traditional/plan-9 options for TYPECASE_WRAPPER shows that it is unaffected by using a plan-9 wrapping of the inner within the outer.
  • RUNCASE=2 (add *&) and RUNCASE=3 (add a cast to the desired reference type) are possible workarounds. Adding *& works for a traditional wrapper but not for a plan-9 wrapper. Adding a cast always works.
  • The variations for RUNCASE in 4..7 show that no magic coming from the assignment operator is at issue, rather, that seeking a _reference_ to the contained element seems to be the problem (as in 6 and 7 being broken), while mere mention of the contained element works (as in 4 and 5 succeeding).

Change History (0)

Note: See TracTickets for help on using tickets.