Skip to content

-aot defines a struct before the struct it holds by value when the inner struct points back at it, so the generated C++ does not compile (field has incomplete type) #4109

Description

@lookibed

daslang 0.6.4 at master 237df6024, Linux x86_64 (WSL2, kernel 6.6), gcc 11.4, Release.

When a struct holds another struct by value and that inner struct points back at the outer one, -aot emits the outer struct's definition before the inner one's. C++ needs a complete type for a by-value member, so the generated file does not compile. The interpreter runs the same program; the -jit and -aot tiers disagree with it only because the C++ never builds.

Reproduction

back_pointer.das:

options gen2

struct Outer {
    next : Outer?
    embedded : Inner
    tag : int
}

struct Inner {
    owner : Outer?
    a : int
    b : int
}

[export]
def main() {
    var o = Outer(tag = 7, embedded = Inner(a = 1, b = 2))
    o.embedded.owner = unsafe(addr(o))
    print("{o.embedded.owner.tag} {o.embedded.a + o.embedded.b}\n")
}
$ daslang back_pointer.das
7 3
$ daslang -aot back_pointer.das back_pointer.das.cpp
$ g++ -std=c++17 -fsyntax-only -I include back_pointer.das.cpp
back_pointer.das.cpp:345:18: error: field 'embedded' has incomplete type 'das::_anon_...::Inner'
back_pointer.das.cpp:348:28: error: static assertion failed: structure size mismatch with DAS
back_pointer.das.cpp:351:34: error: static assertion failed: structure field offset mismatch with DAS

The generated file, in order:

struct Outer {
    struct Outer * next;
    struct Inner embedded;     // Inner is not defined yet
    int32_t tag;
};
...
struct Inner {
    struct Outer * owner;
    ...

Declaring Inner before Outer in the source is a workaround; a pointer member (owner : Outer?) needs only a forward declaration, which the emitter already writes for every struct.

Expected

The emitter orders struct definitions by their by-value dependencies (a struct is defined after every struct it embeds), and treats pointer members as satisfied by the forward declaration. The program compiles under -aot as it runs in the interpreter.

Test

Drops into tests/language/ as test_struct_by_value_back_pointer.das: it passes interpreted, and the AOT lane (test_aot_subset compiles tests/language) fails to build the generated C++ on master.

options gen2
require dastest/testing_boost

struct Outer {
    next : Outer?
    embedded : Inner
    tag : int
}

struct Inner {
    owner : Outer?
    a : int
    b : int
}

[test]
def test_by_value_member_with_a_back_pointer(t : T?) {
    var o = Outer(tag = 7, embedded = Inner(a = 1, b = 2))
    o.embedded.owner = unsafe(addr(o))
    t |> equal(o.embedded.owner.tag, 7)
    t |> equal(o.embedded.a + o.embedded.b, 3)
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions