Skip to content

fix duplicate _Dmain() declaration - #23484

Open
WalterBright wants to merge 1 commit into
dlang:masterfrom
WalterBright:Dmain
Open

fix duplicate _Dmain() declaration#23484
WalterBright wants to merge 1 commit into
dlang:masterfrom
WalterBright:Dmain

Conversation

@WalterBright

Copy link
Copy Markdown
Member

I found the source of the duplicate declaration of _Dmain(), which causes the Mac Arm64 linker to fail. One is the one declared in the user's program, the other was in core.internal.entrypoint, where it was declared in the scope of _d_cmain(). The names were mangled the same (both as _Dmain).

The easiest way to fix that was to pass the correct _Dmain declaration in as an alias argument to the _d_cmain template.

@thewilsonator

Copy link
Copy Markdown
Contributor
  FAILED targets:
  - dshell/dwarf.d
  - compilable/vcg-ast.d

@WalterBright
WalterBright force-pushed the Dmain branch 2 times, most recently from 0ce685b to 65a484a Compare July 29, 2026 04:58
@WalterBright

Copy link
Copy Markdown
Member Author

@thewilsonator thank you!

@WalterBright

Copy link
Copy Markdown
Member Author

where is the file compilable/vcg-ast.d.cg ?

@thewilsonator

Copy link
Copy Markdown
Contributor

it is the output of the test compilable/vcg-ast.d with the flag -vcg-ast

@kinke

kinke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The names were mangled the same (both as _Dmain).

So what was/is the real problem? The user defines _Dmain, the mixin uses it via a fwd declaration. Works for all other platforms, and LDC on macOS arm64 too.,

@Herringway

Copy link
Copy Markdown
Contributor

I don't understand how this could possibly cause a failure. Is the linker crashing? Is there an error message?

@WalterBright

Copy link
Copy Markdown
Member Author

The problem is it produces two entries for _Dmain in the symbol table. One is N_SECT, the other is N_UNDF. Both appear in relocations.

Symbol Table
------------
[ i] type sect desc     value             name
[ 0]  x0e    5  x00 x00000050        EH_frame0 N_SECT
[ 1]  x0e    6  x00 x000000b4            _TMP0 N_SECT
[ 2]  x0e    5  x00 x00000064       _D main.eh N_SECT
[ 3]  x0e    5  x00 x0000008c _hello._d_cmain!().main.eh N_SECT
[ 4]  x0f    1  x00 x00000000          __Dmain N_EXT N_SECT   ******************************
[ 5]  x0f    1  x00 x00000020            _main N_EXT N_SECT
[ 6]  x0f    3  x00 x00000040 __D5hello12__ModuleInfoZ N_EXT N_SECT
[ 7]  x01    0  x01 x00000000          _printf N_EXT N_UNDF
[ 8]  x01    0  x01 x00000000          __Dmain N_EXT N_UNDF       **************************
[ 9]  x01    0  x01 x00000000     __d_run_main N_EXT N_UNDF

The error:

address=0x120 not in any section in 'hello.o'

Sending the object file through chatgpt says the duplicate symbols in the symbol table is invalid.

@WalterBright

Copy link
Copy Markdown
Member Author

What I'm having difficulty with is what is the path to the test case? I found it for the others, but not Ubuntu.

@WalterBright

Copy link
Copy Markdown
Member Author

Chatgpt sez:

**symbol-table problem**

Your symbol table still contains both a defined and undefined __Dmain:

defined:    __Dmain, section 1, value 0
undefined:  __Dmain, value 0

Likewise for _main.

Remove the duplicate undefined entries. A symbol name should not normally occur as both a definition and an undefined reference in the same object.

I already removed the duplicate _main in a previous PR.

@kinke

kinke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Well this seems to be a DMD-backend-specific issue then (for macOS arm64), no need to change druntime because of this.

@kinke

kinke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Apparently not just macOS arm64 specific AFAICT.

maintest.d:

void main() {}

Using DMD v2.112.0 on Linux::

$ dmd -c maintest.d
$ nm maintest.o
0000000000000000 t 
0000000000000000 R _D8maintest12__ModuleInfoZ
                 U _d_dso_registry
0000000000000000 W _Dmain
                 U _Dmain
                 U _d_run_main
                 U _GLOBAL_OFFSET_TABLE_
                 U _main
0000000000000000 W main
                 U __start_minfo
                 U __stop_minfo

LDC v1.42.0 in comparison:

$ ldc2 -c maintest.d
$ nm maintest.o 
0000000000000000 V _D8maintest11__moduleRefZ
0000000000000000 D _D8maintest12__ModuleInfoZ
0000000000000000 T _Dmain
                 U _d_run_main
0000000000000000 T main

So I guess it's just the Apple linker which complains about DMD not folding the 2 declarations to the single definition in that one object file.

@WalterBright

Copy link
Copy Markdown
Member Author

I've had a number of issues with the Apple Arm64 linker in that it adds undocumented requirements to the object file format. None of the other linkers have a problem with this. The fix is in the dmd front end, so it shouldn't affect your work at all.

If I was to change the dmd backend to fold it, it would require building a symbol table, which will significantly slow down the compiler.

@kinke

kinke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

If I was to change the dmd backend to fold it, it would require building a symbol table, which will significantly slow down the compiler.

Yeah I was afraid of something like that. If it's really such a slow-down, maybe you can only use it for macOS arm64, as the Apple linker seems pickier than for x86_64 and require these unique symbols?

Note that users would also hit this with similar code, compiling a module with the definition and another module with a fwd-declaration to the same object file. Can also happen for weird code like this:

void foo() {}

pragma(mangle, foo.mangleof)
void bar();

// reference bar
void baz() { bar(); }

@WalterBright

Copy link
Copy Markdown
Member Author

You're right about the pragma mangle thing, but I regard that as "user should know what he is doing" when doing things like that, similar to using inline assembler.

DMD can compile entire programs as one object file (I build DMD that way!), and that means a very very large number of symbols and corresponding relocations that would have to be merged.

The existing _d_cmain template is relying on a trick that relies on special mangling of main. Relying on this trick is undocumented in core.internal.entrypoint, and is poor form, especially when using an alias parameter solves the problem in a simple, straightforward manner.

@kinke

kinke commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

This mangle trick is also how externDFunc works. So I'm afraid you won't be able to get away that easily by working around this one/first case here.

@rainers

rainers commented Jul 29, 2026

Copy link
Copy Markdown
Member

You also get multiple symbol table entries without pragma(mangle):
[Edit: simplified the example further]

module mod;

extern(C) int foo();

int main()
{
	return foo();
}
module mod1;

extern(C) int foo()
{
	return 2;
}

Compiled to a single object file:

(dmd-2.112.0)/tmp/d$ dmd mod.d mod1.d
(dmd-2.112.0)/tmp/d$ objdump -t mod.o | grep foo
0000000000000000 l    d  .text.foo      0000000000000000 .text.foo
0000000000000000  w    F .text.foo      0000000000000006 foo
0000000000000000         *UND*  0000000000000000 foo

Also happens on Windows:

C:\tmp\d>c:\d\dmd-2.112.0\windows\bin\dmd.exe mod.d mod1.d

C:\tmp\d>dumpbin /symbols mod.obj | grep foo
014 00000000 SECT5  notype ()    External     | _foo
01E 00000000 UNDEF  notype ()    External     | _foo

This pretty much matches the _Dmain case.

@rainers

rainers commented Jul 29, 2026

Copy link
Copy Markdown
Member

where is the file compilable/vcg-ast.d.cg ?

The reference file is compilable/extra-files/vcg-ast.d.cg

@WalterBright

Copy link
Copy Markdown
Member Author

oh hell

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants