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

How to standardize function calls LLVM lowers to? #84

Closed
alexcrichton opened this issue Mar 8, 2018 · 13 comments
Closed

How to standardize function calls LLVM lowers to? #84

alexcrichton opened this issue Mar 8, 2018 · 13 comments

Comments

@alexcrichton
Copy link
Contributor

LLVM will intrinsically lower some operations to a function call at runtime, for example this program:

#![crate_type = "lib"]

pub fn foo(a: f32) -> f32 {
    a.log2()
}

generates IR that looks like:

define float @foo(float %a) unnamed_addr #0 {
start:
  %0 = tail call float @llvm.log2.f32(float %a) #2
  ret float %0
}

note the lack of call to a function named log2, it's just an LLVM intrinsic. On Linux, however, at runtime this function is compiled as calling the symbol log2f, usually found in libm.

It can be important in some situations to use the intrinsics rather than explicit functions themselves as LLVM may optimize better, but other times LLVM may optimize a program to using an intrinsic without the original source mentioning the intrinsic. In other words, these function calls can sometimes run a risk of being inserted even if they're not used!

Currently LLVM will translate this in wasm as an import from the env module with the same name as these functions have in C. Additionally the standard library has a set of symbols that it imports and uses. (if the associated functions are called).

Today projects like wasm-bindgen will automatically fill in these imports so users don't have to worry about them, but that's not necessarily great! The method in which these intrinsics are exposed today may not be stable and could change tomorrow.


Ok so that brings us to the question, what do we do about these math-related intrinsics? some options:

  • We could prevent any access at all to these intrinsics. Downsides of this approach are that simple operations like a % b wouldn't work for floats. Additionally I'm not sure that if we forbid access that it'd actually work, for example are we sure that LLVM won't optimize otherwise normal code to have these imports?
  • Define these functions for bundlers/wasm instantiators. We could simply define that these functions may be imported, and we'd also define "here's the symbol they're gonna use and here's the expected behavior". The good news is that everything here I believe is pretty well defined in terms of what it's supposed to do (aka C header files and whatnot). The downside is that there's a lot of these to work with (aka see wasm-bindgen's list)
  • Polyfill everything in rustc as a postprocessing step over the wasm. This would entail having a software solution available for all of these intrinsics (not that I have any idea how to write atan) and rustc would inject an implementation into the wasm. The good news here is that everything's "always taken care of", the bad news is we're probably filling in a much worse implementation of sin than Math.sin, not to mention that it's probably pretty big code-size-wise.

I'm curious to hear what others think! I'm sort of tempted to take the second route here, defining these functions for bundlers/wasm instantiators. We could, for example, define that these functions will always be imported from something like __rust_math (requiring rustc to postprocess the wasm file) and they've all got their C-like names (reverting libstd-specific names)

@alexcrichton
Copy link
Contributor Author

cc @sunfishcode

@alexcrichton
Copy link
Contributor Author

Also related to https://github.com/rust-lang-nursery/rust-wasm/issues/16, but not so much about I/O per se

@alexcrichton
Copy link
Contributor Author

Discussed a bit with @sunfishcode, and the "port all math to Rust" may not be as difficult as originally thought. Additionally the intrinsics may not be too large, for example MUSL's implementations are relatively reasonably sized. We may be able to lift those or also work off https://github.com/nagisa/math.rs as well

@japaric
Copy link

japaric commented Mar 17, 2018

the "port all math to Rust" may not be as difficult as originally thought.

Let me know if y'all plan to implement libm in Rust. The embedded community, and likely the no-std community in general (e.g. Redox), is interested in having pure Rust math so we are very likely to contribute to such effort.

@alexcrichton
Copy link
Contributor Author

Oh an excellent point @japaric, nice thinking!

I'm not entirely decided what to do about this personally, but I think having a Rust-based solution in our back pocket is probably a good idea no matter what. @japaric perhaps I could itemize what we need in wasm (currently at least) as well as perhaps find the musl implementation of each, and then post a checklist to the compiler-builtins repo? (we'd have all the intrinsics in an off-by-default feature probably there)

@japaric
Copy link

japaric commented Mar 19, 2018

@alexcrichton Sounds like a plan to me!

to the compiler-builtins repo?

Will the math functions live in the compiler-builtins repo though? I would prefer them to live in a separate crate because:

  • compiler-builtins is a (perma?) unstable crate / compiler detail and we, the embedded community, are trying to reduce the number of dependencies on unstable features. Right now we can (mostly) swap compiler-builtins (unstable) for libgcc.a (stable); linking to libm.a is more complicated because of float ABI and FPU presence/absence (e.g. the arm-none-eabi toolchain ships with like 4 different libm.a binaries).

  • The math subroutines will become candidates for inlining (which can result in optimizing out branches, etc). The stuff in compiler-builtins is intentionally not inlineable even when LTO is enabled.

If wasm needs the unmangled symbols in compiler-builtins we can structure things like this:

// crate: (lib)m -- the port of MUSL stuff lives here
#![no_std] // stable (no unstable features)

pub fn sinf(x: f32) -> f32 {
    // ..
}
// crate: compiler-builtins
#![compiler_builtins] // perma-unstable

extern crate m;

// shims for wasm compatibility
// "C" or "aapcs" or w/e makes sense
#[no_mangle]
pub extern "C" fn sinf(x: f32) -> f32 {
    m::sinf(x)
}
// crate: float-core -- for use in no-std crates (i.e. use float_core::FloatExt;)
#![no_std] // stable (no unstable features)

extern crate m;

pub trait FloatExt {
    fn sin(self) -> Self;
    // ..
}

impl FloatExt for f32 {
    fn sin(self) -> Self {
        m::sinf(self)
    }
}

And if it makes sense float-core and m could be merged into a single crate (maybe not a good idea?).

@alexcrichton
Copy link
Contributor Author

@japaric hm an interesting idea! I'd assumed compiler-builtins due to its extensive CI, but you bring up some excellent points. There's certianly no need for these intrinsics to be so unstable!

I do think though that they may still participate in linker shenanigans and not be available for LTO (or at least not all of them). For example I don't think that LLVM knows that the frem instruction will be lowered on some targets to fmod (the function call). That means if you LTO the fmod function itself it runs the risk of being gc'd despite it actually being needed after code generation.

I'm also not sure if LLVM's smart enough to draw a connection between the fma intrinsic and the fma function itself as well...

In any case doing this all in a separate crate is a great way to start. We can always figure out weird integration bugs later down the road once we've actually got a body of code.

@alexcrichton
Copy link
Contributor Author

@alexcrichton
Copy link
Contributor Author

Er, above is a list of what wasm needs (although embedded targets probably need more like sqrt)

@tshort
Copy link

tshort commented Mar 21, 2018

Julia has native versions of most of libm. Much of that is in this directory. It's all MIT-licensed code. That may help when writing Rust versions. The Julia community is also slowly working on wasm support, so eventually, it may be possible to use basic Julia functions like these compiled to wasm.

@japaric
Copy link

japaric commented Jul 12, 2018

@alexcrichton

I finally got around starting a port of MUSL libm. The repo lives here. The test infrastructure is
already in place -- the approach is similar to the one used in this repo but uses MUSL libm
(x86_64-unknown-linux-musl) to compute the expected results.

To cover what WASM needs I think we should add that repo as a submodule here in compiler-builtins, do a source import of
the math functions (#[path = ..] mod math) and create #[no_mangle] shims around the functions
WASM needs. And probably write some sort of "it must link" test for WASM in that repo.

There are still lots of function that need to be ported. Happy to add more reviewers to the repo and
to hand over the repo to rust-lang-nursery for visibility.

P.S. long term we may want to provide (overridable (*)) math support in core as inherent methods
of f32 / f64.

(*) Some people may want to override the Rust implementation with assembly tuned ones.

@alexcrichton
Copy link
Contributor Author

@japaric that's awesome! Thanks for the pointer!

Your proposed integration sounds spot on for compiler-builtins as well.

@alexcrichton
Copy link
Contributor Author

With https://github.com/japaric/libm, rust-lang/compiler-builtins#248, rust-lang/rust#52499, @japaric, and an awesome amount of elbow grease from the community, I think we can safely say this is done now!

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

No branches or pull requests

3 participants