Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exportc mangles as C++ and doesn't work with importc(C) with nim cpp #10578

Closed
timotheecour opened this issue Feb 6, 2019 · 5 comments · Fixed by #12144
Closed

exportc mangles as C++ and doesn't work with importc(C) with nim cpp #10578

timotheecour opened this issue Feb 6, 2019 · 5 comments · Fixed by #12144

Comments

@timotheecour
Copy link
Member

timotheecour commented Feb 6, 2019

Nim should allow controlling symbol mangling; it currently seems impossible with nim cpp.

Using test example below:

# this works
nim c -r main.nim

# this errors: Undefined symbols for architecture x86_64: _my_foobar1
nim cpp -r main.nim

test example

main.nim:

proc foobar1(a: int) {.importc: "my_foobar1".}
proc main() = foobar1(10)
main()

# intentially putting this at the end;
# other use cases: this could be linked from a shared library (nim plugin)
# other use cases: forward declaration to avoid module import cycles
import ./t0202b

t0202b.nim:

proc foobar1(a: int) {.exportc: "my_foobar1".} = echo (a:a)

very partial workaround

Here's my current workaround that works with nim c and nim cpp
It still doesn't let me mangle a c++ symbol the way I want, so it doesn't help if I need to export a symbol for a non-nim program that expects a given mangling over which I have no control.

# main.nim:
proc getMangle(cName, cppName: string): string =
  when defined(cpp): cppName else: cName

proc foobar1(a: int) {.importc: getMangle("my_foobar1", "_Z10my_foobar1x").}
...

# t0202b.nim:
proc foobar1(a: int) {.exportc: "my_foobar1".} = echo (a:a)

workaround suggested by @Araq: I couldn't make it work

you need to play tricks with emit "extern C" or .codegenDecl

no success, please explain

  • I tried:
var foobar2 = proc(a: int) {.codegenDecl: "$1 foobar2_b $3".} = discard

# gives compilation error for t0202b.cpp:
error: use of undeclared identifier 'colonanonymous__t4FhpLqaEhvD69cnCq6B8Sg'
        foobar2_tPSgPPlTA53EAR1UTPkutw = colonanonymous__t4FhpLqaEhvD69cnCq6B8Sg;
  • I also tried:
proc foobar2(a: int) {.codegenDecl: "$1 my_foobar1 $3".} = echo (a:a)
# this elides `foobar2` from `t0202b.cpp` as the symbol is unused, so gives a link error for main

# but if i add this to avoid eliding the symbol:
if false: foobar2(0)
# then I get:
error: use of undeclared identifier 'foobar2_t4FhpLqaEhvD69cnCq6B8Sg'

workaround via emit: couldn't make it work

{.emit:"""extern "C"""".}
proc foobar2(a: int) {.exportc: "nim_foobar2".} = echo (a:a)

when you compile, it seems to work (no error!) but doesn't actually work:
the generated cpp code is:

extern "C"
N_NIMCALL(void, nim_foobar2)(NI a);
....

// NOTE: no `extern "C"` here so it doesn't relate to above declaration and won't be visible by outside
N_NIMCALL(void, nim_foobar2)(NI a) {
       ...
	echoBinSafe(T1_, 1);
	popFrame();
}

[EDIT] (credits: @Araq)

this seems to work:

proc foobar1(a: int) {.exportc: "my_foobar1", codegenDecl: """extern "C" $1 $2 $3""".} = echo "ok1"

however, it breaks nim c compatibity; so, see proposal below.

proposal

rule 1

when exportc is specified, it should imply extern "C" so that it works seamlessly in tandem with importc ; that's the most common use case and the one that'll lead to least bugs when we need to support both nim c and nim cpp (either in isolation, or even when a nim c main links against a nim cpp shared library)
Eg:

# this should mangle to same symbol `my_foobar1` regardless we use `nim c`  or `nim cpp`
proc foobar1(a: int) {.exportc: "my_foobar1".} = echo (a:a)
# ditto, mangles to "foobar1"
proc foobar1(a: int) {.exportc.} = echo (a:a)
# ditto, mangles to "foofoobar1bar"
proc foobar1(a: int) {.exportc: "foo$1bar".} = echo (a:a)

rule 2

We also allow (but as an opt-in option) for exportc to mangle as c++ as follows:

# this should mangle to same symbol `__Z10my_foobar1x` (`c++filt __Z10my_foobar1x  = my_foobar1(long long)`)
proc foobar1(a: int) {.exportc: "my_foobar1", mangling: cpp.} = echo (a:a)

# ditto with "$1" which only affects the function name before mangling via function arguments

note:

  • {.push: mangling: cpp.} can be used for convenience to wrap lots of procs at once
  • in D, there is pragma(mangle) for this purpose;
    it allows flexibilty, for example by conrolling how a particular type Foo gets mangled (say, to FooAlias), so that when a type Bar that depends on it (eg std::vector<Foo>) gets mangled (using c++ mangling rules), it'll use the name FooAlias inside the mangling of Bar , as if Foo was named FooAlias in the 1st place.
  • [EDIT] mangling can support different options, eg:
    • mangling:native : use C or C++ mangling depending on whether nim c or nim cpp is used
    • mangling: cpp: use C++ mangling with nim cpp, and currently errors with nim c` but could add support in future (eg link or by calling c++ codegen for the declaration along with its minimal needed dependencies )
    • mangling: objc, mangling: d (reserved for future use)
@alaviss
Copy link
Collaborator

alaviss commented Feb 7, 2019

{.mangling.} is sorta misleading as Nim doesn't "mangle" these symbols, the C++ compiler does.

A proposal of mine would be a {.exportnative.} pragma that basically means: export the symbol in the form native to the targeted backend (ie. export as C++ symbol when used with C++ backend). It would have the same usage as {.exportc.}

@zielmicha
Copy link
Contributor

The "emit" trick actually works:

nm a | grep nim_foobar2                                                                                                                                                                                                                             0000000000013d15 T nim_foobar2

In C++ extern "C" is only needed for declaration, not for definition.

@timotheecour
Copy link
Member Author

The "emit" trick actually works:

indeed;

here's a better version of both approach that works with both nim c and nim cpp:

const externCDecl = when defined(cpp): """extern "C" $1 $2 $3""" else: """$1 $2 $3"""
proc foobar1(a: int) {.exportc: "my_foobar1", codegenDecl: externCDecl.} = echo "ok1"
# I added NIM_EXTERNC in the spec in https://github.com/nim-lang/Nim/pull/10022 so this should continue to work:
{.emit: "NIM_EXTERNC".}
proc foobar1(a: int) {.exportc: "my_foobar1".} = echo "ok1"

@alaviss

A proposal of mine would be a {.exportnative.} pragma that basically means: export the symbol in the form native to the targeted backend (ie. export as C++ symbol when used with C++ backend). It would have the same usage as {.exportc.}

if the semantics are as follows then that'd be ok (that's basically rule 1 plus a special case of rule 2: mangling:native => exportnative ):

proc foobar1(a: int) {.exportc: "my_foobar1".} = echo (a:a) # mangles as `my_foobar1` with both `nim c` or `nim cpp` (ie, implies `extern "C"` for `nim cpp`)
proc foobar1(a: int) {.exportc: "my_foobar1", exportnative.} = echo (a:a) # mangles as `my_foobar1` with `nim c` or (mangled C++ name) for `nim cpp`

@alaviss
Copy link
Collaborator

alaviss commented Feb 8, 2019

I'd mean something like this: proc foobar1(a: int) {.exportnative: "my_foobar1".}. The semantics are the same as you described, just different syntax.

@kaushalmodi
Copy link
Contributor

kaushalmodi commented Apr 26, 2019

I ended up on this issue today while trying to make Nim->C++ compiled .so work with a foreign language. I ended up doing:

when defined(cpp):
  {.push codegenDecl: """extern "C" $1 $2 $3""".}

proc hello() {.exportc.} =
  echo "Hello from Nim!"

when defined(cpp):
  {.pop.}

@Araq But I wonder why that extern "C" bit isn't auto-added for {.exportc.} procs when compiling to C++.


Seems like this SO answer answers that:

extern "C" determines how symbols in the generated object file should be named. If a function is declared without extern "C", the symbol name in the object file will use C++ name mangling.

kaushalmodi added a commit to kaushalmodi/nim-systemverilog-dpic that referenced this issue May 6, 2019
kaushalmodi added a commit to kaushalmodi/nim-systemverilog-dpic that referenced this issue May 6, 2019
timotheecour added a commit to timotheecour/Nim that referenced this issue Aug 21, 2019
timotheecour added a commit to timotheecour/Nim that referenced this issue Sep 1, 2019
timotheecour added a commit to timotheecour/Nim that referenced this issue Sep 5, 2019
Araq pushed a commit that referenced this issue Sep 6, 2019
* fixes #10578
* add tests
* add changelog
* add {.exportcpp.}
timotheecour added a commit to timotheecour/Nim that referenced this issue Sep 6, 2019
timotheecour added a commit to timotheecour/Nim that referenced this issue Sep 7, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants