Autolinking disabled on embedded?

Hiya,

I've got a module I'm building that is half C and half Swift, I'm trying to link it into a program with all swift compiled in embedded mode. I used to do import AVR (the module is called AVR rather confusingly) in my programs and the object file would contain a swift1_autolink_entries that listed linker commands for the static libraries I needed, like -lAVR. So I could adapt my tooling to read this and add it to the link.

(side note: Given I'm using llvm, it should have been possible via llvm metadata, but that might be a limitation of linking ELF rather than macho.)

But either way, the issue is since I turned on embedded mode on the compiler, this section doesn't seem to be emitted any more. And I've no way of automatically getting the static libraries required to put into the link now.

Looking at the code in IRGenModule.cpp it looks like perhaps this might be the trouble...

void IRGenModule::addLinkLibrary(const LinkLibrary &linkLib) {
  // The debugger gets the autolink information directly from
  // the LinkLibraries of the module, so there's no reason to
  // emit it into the IR of debugger expressions.
  if (Context.LangOpts.DebuggerSupport)
    return;

  if (Context.LangOpts.hasFeature(Feature::Embedded))
    return;
...

...as I read this, in embedded mode, auto linking is automatically disabled. Is there a reason this has to be the case? Is there perhaps another mechanism I should be using?

Regards,
Carl

1 Like

Embedded Swift currently doesn't link Swift libraries in the traditional way, instead library code is "pulled" into the client module's build as needed (this is somewhat documented at https://github.com/swiftlang/swift/blob/main/docs/EmbeddedSwift/UserManual.md#libraries-and-modules-in-embedded-swift). So I'd expect that in many cases you won't need to link the .a files for libraries at all (that is the case e.g. for the stdlib). That's the reason why autolinking is disabled for Embedded Swift.

Do you actually need to link your libraries? What are they, can you explain your setup?

1 Like

Ok, makes sense. I see.

This might be somewhat me using the compiler in a somewhat unintended way.

Our main hardware module, which I call “AVR” is hybrid. Most of the logic is written in C and the functions e.g. “_digitalWrite” that then have small Swift “shims” over the top like “digitalWrite” that mostly just act as a pass through, creating slightly nicer Swift interfaces and calling the underlying C function.

Both the C and the Swift code end up in the same module and the same standard library. I have a modulemap file that defines the AVR module with headers for the C functions and a link clause for the static library.

The swift files are compiled with “import-underlying-library” then the object files compiled from the swift source and the object files compiled from the C source code are all collated into one static library.

Before embedded Swift, importing this library into a Swift program would result in linking the static library and it all worked.

I’m thinking there are a few ways forward.

Probably the obvious think to do is separate these files into two modules. One is written in embedded Swift and it imports the other which is a classic C module. That’s probably a bit more canonical. I think hybrid modules are odd. And I think they’re not well supported by SPM when we manage to get that working.

Hiya, I'm stuck on this same issue again @kubamracek!

There was a hiatus because I had to do some extensive hardware library work on another project. Now I'm back on Embedded Swift-ify-ing everything on our platform again.

We are 99% there but for some C code. I had a slightly odd structure before, with a hybrid C and Swift module, which is a bit odd and wasn't working well. So that's now two modules. One pure Swift module called "AVR" that mostly contains stubs and suchlike. When compiling that, I'm only making AVR.swiftmodule (as recommended in swift/docs/EmbeddedSwift/UserManual.md at main · swiftlang/swift · GitHub) ... and that's getting included into projects just fine, all working perfectly as advertised.

However, the swift code in the AVR module references another module, which is written in C and compiled with clang and gcc, called "CAVR" ... which contains a lot of the "meat" of the function implementations. This second module is imported into AVR swift code exactly as you'd expect...

For example...

import CAVR

// public typealias AVRString = UnsafePointer<Int8>
// public typealias CString = UnsafePointer<Int8>

public typealias AVRString = Optional<UnsafePointer<Int8>>
public typealias CString = Optional<UnsafePointer<Int8>>

// USART

/*
 * USART - standard serial via RX/TX pins, e.g. for debugging
 */

/// Start the UART/serial on pins 0/1 as RX/TX.
/// Baud rate runs at the default rate 57600, matching the IDE default.
/// If you want a more complex setup, use SetupSerial(baudRate:,cpuClockRatekHz:).
@inlinable
public func SetupSerial() {
	_setupSerial(57600)
}

...the CAVR module builds and produces a static library libCAVR.a with the compiled clang/gcc object files in it and the clang module.modulemap file looks pretty normal from what I know of them...

module CAVR {
	header "CAVR.h"
	export *
	link "CAVR"
}

The issue now is when I build projects that import AVR, they have undefined symbols (for example _setupSerial) in the link. And the only way I can get the link to work is to manually add something like -lCAVR to the linker command.

The swift auto link entries are again not there, even though this is a pure/classic/traditional clang module that I'm importing (transitively, via importing the AVR module).

This is unexpected behaviour to me. How should this be working for transitive dependencies like this?

Cheers,
Carl