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

Add Crystal::Loader #11434

Merged
merged 6 commits into from
Nov 25, 2021
Merged

Conversation

straight-shoota
Copy link
Member

@straight-shoota straight-shoota commented Nov 11, 2021

This patch adds a linker/loader component to the compiler. We need this for interpreted mode and it imitates the behaviour of ld.so/ld for linking and loading dynamic libraries at runtime.

It's a preparatory step for #11159 and adds nothing by itself. But it's an isolated non-trivial component that we can discuss it on its own.

It provides the following functionality:

  • parse linker arguments (e.g. -L/usr/local/mylibs -lfoo /path/to/libbar.so)
  • locate the specified libraries and load them into memory (using libdl)
  • find symbols in the opened libraries and return their addresses

The symbol addresses serve as a point to call in via libffi (not included in this PR).

There are a number of differences compared to linking object files in a normal
object file linking process. Most are caused by limitations of libdl.

  • Only dynamic libraries can be loaded. Static libraries and object files
    are unsupported.
  • All libraries are loaded into the same namespace. That means libraries
    loaded in the compiler program itself may provide symbols to libraries
    loaded with Crystal::Loader. Symbols may be available without explicitly
    mentioning their libraries. It might be impossible to link against other
    verions of the libraries that the compiler is linked against.
  • A fully statically linked compiler may help dealing with the previous
    issue. But using libld in a non-dynamically loaded executable might cause
    other issues.

Probably we'll discover more limitations and differences over time.

Resolves #11173

@asterite
Copy link
Member

That means libraries
loaded in the compiler program itself may provide symbols to libraries
loaded with Crystal::Loader

Will Crystal::Loader be publicly exposed? I ask because if you use Crystal::Loader in a program other than the compiler, then that sentence is not quite right. It should be "That means libraries loaded in the program that uses Crystal::Loader may provide..."

(I saw this comment in the source code too, that's why I ask)

@straight-shoota
Copy link
Member Author

I don't think it should be part of the public API. It's in the compiler source tree. Maybe that could change at some point, if there's demand for it. But that might need other alternations as well.
But I guess I wouldnt't hurt to generalize the comment.

@asterite
Copy link
Member

Oh, if it's not meant to be publicly exposed than it's fine to leave the comment as is.

Copy link
Contributor

@maxfierke maxfierke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks awesome! One suggestion on automatically supporting Homebrew-based libs on ARM64


{% if flag?(:darwin) %}
default_search_paths << "/usr/lib"
default_search_paths << "/usr/local/lib"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add the /opt/homebrew/lib path for ARM64 Darwin? Or do that as a patch on the Homebrew-side? (This would mirror the behavior of the FFI gem, for example)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep the internals exactly as in ld to avoid confusion between linking in interpreted and compiled mode. Any user-defined paths must be configured via DYLD_LIBRARY_PATH or CRYSTAL_LIBRARY_PATH.

@j8r
Copy link
Contributor

j8r commented Nov 16, 2021

How the @[Link] annotation will be handled at runtime, will this loader be used too?

@asterite
Copy link
Member

Yes. Check the interpreter branch to see how it's done now.

@straight-shoota straight-shoota marked this pull request as ready for review November 16, 2021 14:11
Copy link
Member

@beta-ziliani beta-ziliani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some minor comments

src/compiler/crystal/loader.cr Outdated Show resolved Hide resolved
src/compiler/crystal/loader/unix.cr Outdated Show resolved Hide resolved
# verions of the libraries that the compiler is linked against.
# * A fully statically linked compiler may help dealing with the previous
# issue. But using `libdl` in a non-dynamically loaded executable might cause
# other issues.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of issues? Or is it an unknown?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unknown (at least to me).

There are projects such as https://github.com/mittorn/custom-linker to mitigate this. I did not encounter any problems in my (limited) tests with statically linked binaries, however. I guess we'll see how this plays out.

src/compiler/crystal/loader/unix.cr Outdated Show resolved Hide resolved
src/compiler/crystal/loader/unix.cr Outdated Show resolved Hide resolved
File.each_line(path) do |line|
next if line.empty? || line.starts_with?("#")

if include_path = line.lchop?("include ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is include <path> (space before and/or after include) considered invalid in ld.so.conf?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a space between include and the path. I'm not sure if more whitespace is allowed or not. Couldn't find a proper format specification (if there even is one). All examples I've looked at have no extra whitespace.

Comment on lines +36 to +50
# def find_symbol?(name : String) : Handle?
# raise NotImplementedError.new("find_symbol?")
# end

# def load_file(path : String | ::Path) : Handle
# raise NotImplementedError.new("load_file")
# end

# private def open_library(path : String) : Handle
# raise NotImplementedError.new("open_library")
# end

# def self.default_search_paths : Array(String)
# raise NotImplementedError.new("close_all")
# end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commented code should be removed, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it there to define the interface that is expected to be implemented in the platform-specific files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do the same thing in with platform-specific implementations in Crystal::System.

Comment on lines +83 to +85
if File.exists?(path)
yield path
end
Copy link
Contributor

@Sija Sija Nov 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small stylistic suggestion:

Suggested change
if File.exists?(path)
yield path
end
yield path if File.exists?(path)

Copy link
Member

@beta-ziliani beta-ziliani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀

@straight-shoota straight-shoota added this to the 1.3.0 milestone Nov 23, 2021
@straight-shoota straight-shoota merged commit 8f0830c into crystal-lang:master Nov 25, 2021
@straight-shoota straight-shoota deleted the feature/loader branch November 25, 2021 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

C bindings in interpreted mode
6 participants