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 global_asm! for module-level inline assembly #1548

Merged
merged 1 commit into from
Jul 29, 2016

Conversation

Amanieu
Copy link
Member

@Amanieu Amanieu commented Mar 18, 2016

Example:

global_asm!(r#"
.globl my_asm_func
my_asm_func:
    ret
"#);

extern {
    fn my_asm_func();
}

Rendered

@nagisa
Copy link
Member

nagisa commented Mar 18, 2016

In my limited knowledge module-level assembly in LLVM is not very well supported.

@Amanieu
Copy link
Member Author

Amanieu commented Mar 18, 2016

I've used it in Clang before and it worked fine for me. It was added to LLVM relatively recently however, so there may have been bugs in older versions.

@Ericson2314
Copy link
Contributor

I've been a fan of module level assembly over naked functions for a while. It would be nice however if LLVM supported extended module level assembly, i.e. that with constraints. Symbol and constant constraints do make sense at least.

@archshift
Copy link
Contributor

As the naked functions RFC was just accepted, is this still necessary?

@Amanieu
Copy link
Member Author

Amanieu commented Mar 21, 2016

It's still useful as a way of including external assembly files.

@Ericson2314
Copy link
Contributor

@Amanieu [edit @archshift] As the naked functions RFC was accepted as an experiment, I think we absolutely should still work to merge this and arbitrary LLVM calling convention support. Experiments needs controls.

@comex
Copy link

comex commented Mar 22, 2016

Module-level inline assembly can also be useful for miscellaneous things other than functions, e.g. .symbol_resolver on OS X.

@huonw
Copy link
Member

huonw commented Mar 22, 2016

Is there a particular reason this needs to be a separate macro? i.e. can we just use asm!("...") at the top level?

@Amanieu
Copy link
Member Author

Amanieu commented Mar 22, 2016

asm! accepts a set of input, output and clobber constraints, which are not allowed for module-level asm. We could still use asm! but we would need to ensure no constraints are specified for top-level blocks.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Mar 31, 2016
@huonw
Copy link
Member

huonw commented Apr 7, 2016

@Amanieu that seems like a perfectly reasonable restriction (asm!("...": x); at the top level could trigger error: constraints are invalid for module level asm!).

@Amanieu
Copy link
Member Author

Amanieu commented Apr 7, 2016

@huonw That is fine too I guess. I decided to use a separate name because module-level assembly is quite different from function-level assembly.

@Ericson2314
Copy link
Contributor

As I said above, some sort of constraints do make sense for top-level assembly. Therefore, I like using asm!(..) for both, in case the the features eventually grow more similar.

@nrc nrc added the I-nominated label Jul 7, 2016
@nrc
Copy link
Member

nrc commented Jul 7, 2016

Nominating for discussion at lang meeting. I'm in favour of this RFC in the abstract. I guess the big question is whether, given we've accepted naked functions, whether we want this facility as well.


There are two main use cases for this feature. The first is that it allows functions to be written completely in assembly, which mostly eliminates the need for a `naked` attribute. This is mainly useful for function that use a custom calling convention, such as interrupt handlers.

Another important use case is that it allows external assembly files to be used in a Rust module without needing hacks in the build system:
Copy link
Member

Choose a reason for hiding this comment

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

Could you detail what exact build system hacks are required currently to support external asm files?

Copy link
Contributor

Choose a reason for hiding this comment

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

You need an external assembler. Where is it? Is it the right version? Does it support the target platform(s)? Does it output in the proper location? etc etc. All this makes for ugly build.rs boilerplate, and worse incremental rebuilds.

This aspect of the motivation is exactly the same as that for naked functions.

@Amanieu
Copy link
Member Author

Amanieu commented Jul 7, 2016

There is actually a rather significant downside, which I've only noticed recently: you don't get any line debug information with global_asm!. With external asm files you do get line debug information which allows gdb to show you the line in the asm file corresponding to the current instruction.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Jul 7, 2016

@Amanieu, not that it wouldn't be a lot of work, but I suspect that include_str and asm could be improved to fix this---maybe LLVM needs work too. In other words it's a pain but not a necessary limitation.

@nagisa
Copy link
Member

nagisa commented Jul 7, 2016

My personal opinion that all that we really need to do here is create an assembly counterpart of llvm_build_utils crate. Not only it would support having proper debuginfo, you wouldn’t be restricted to the weird LLVM favour of assembly which supports no features assembly programmers would expect for any larger stride of assembly (macros?)

Is there really any use for having a big chunk of assembly code defining functions inline with rust code?

@nrc nrc added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed I-nominated labels Jul 7, 2016
@nrc
Copy link
Member

nrc commented Jul 7, 2016

🔔 This RFC is now entering its final comment period 🔔

@strega-nil
Copy link

I would rather have module level assembly use the asm!macro, perhaps with different necessary things, and losing clobbers, but still using the same macro.

I would really like us to completely redo our asm! system.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jul 18, 2016

I haven't seen much discussion about the comparison between this and naked functions. Personally, I'm not opposed to module-level assembly -- and definitely prefer it to naked functions in some respects, for example it's (relatively) clear semantics -- but it seems like being able to replace naked functions is a pretty serious motivator.

cc @jackpot51 and @tari for their thoughts here.

I agree with @ubsan that it'd probably be nicer to use the same asm! syntax, but then again it's commonly the case that "module-level items" in Rust are distinct from "things in functions" (for example, we write static vs let), so having a distinct macro seems ok (consider that global_asm could also be used within a fn, presumably).

Do we need to provide any particular guarantees as to where in the generated assembly this section appears? In general it feels like, while the semantics are fairly clear, this RFC does leave a fair number of unknowns (it's pretty short, for one thing).

Considering all this, I think overall I am currently inclined to close and maybe revisit later when we have gained more experience with naked fns.

@Amanieu
Copy link
Member Author

Amanieu commented Jul 19, 2016

I personally think adding naked functions was a bad idea, so I proposed this RFC as an alternative. I just feel that naked functions are too hard to use correctly since the only way to use them is if the entire contents of the function is just a single asm! block.

Regarding the syntax, I chose a different name since global_asm! works quite differently from a normal asm! block, mainly the fact that global_asm! does not support constraints and parameters, it only accepts a single string of asm code.

Allowing global_asm! in functions is tricky: what does it mean to have global_asm! in an inline function, is the asm also included in the caller's crate? What about a generic function, is the global_asm! replicated for each monomorphization?

@tari
Copy link
Contributor

tari commented Jul 19, 2016

There is actually a rather significant downside, which I've only noticed recently: you don't get any line debug information with global_asm!.

That sounds like a LLVM limitation (which would need some rustc support), rather than an inherent problem with this approach. Definitely fixable, but I don't know enough about how LLVM handles debug info to say what it might take.

Allowing global_asm! in functions is tricky: (snip)

I think this is a useful point in favor of making the syntax be asm! (which I think is a nicer choice in general) rather than a new macro, since asm! is not valid outside a function and it wouldn't make sense to permit global_asm! inside functions.

With the interpretation of this feature as filling a hole in current asm! support (permitting it outside functions) rather than a completely new feature, it makes sense to add. On the other hand, the same functionality can be achieved with a naked function, but would require improvements to asm! to reach full feature parity (include_str!).

Comparing the approaches, an item referred to by Rust code requires less boilerplate when implemented as a naked function (no extern items at least), while the source-level overhead of a non-code item as a naked function is only a little higher (though currently unsafe- that's one of those "unknown" calling convention issues).


I just feel that naked functions are too hard to use correctly since the only way to use them is if the entire contents of the function is just a single asm! block.

There's definitely room for improvement with that. The MSVC support for custom prolog/epilog (with non-asm code in the body) came up in the naked RFC discussion, which could be ported in some fashion to LLVM and be supported by rustc. It still requires writing some asm in a function, but permits non-asm code in naked functions as well.

@Ericson2314
Copy link
Contributor

I think @nikomatsakis meant global_asm! in functions would be like other items in functions: just a scoping trick.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jul 21, 2016

Are there use cases beyond naked functions?

EDIT: I mean, are there use cases that cannot be addressed by naked functions?

@parched
Copy link

parched commented Jul 21, 2016

@nikomatsakis Not really, I think it just means you can write

global_asm!(...)

instead of

#[naked]
#[allow(dead_code)]
fn ignore_me_im_some_useless_symbol_that_isnt_used() {
    asm!(...)
}

@Amanieu
Copy link
Member Author

Amanieu commented Jul 22, 2016

@nikomatsakis It also allows you to include external asm files by using include_str!. See the example in the RFC.

@Ericson2314
Copy link
Contributor

Ericson2314 commented Jul 24, 2016

Possibly some data is easier to define in assembly, but I cannot think of any example off-hand. Naked functions would be a poor way to define statics.

@parched
Copy link

parched commented Jul 24, 2016

It also allows you to include external asm files by using include_str!

I think even there you could put it in an asm! in a naked function.

Possibly some data is easier to define in assembly, but I cannot think of any example off-hand.

Yes a vector table often has to be done in assembly, which I've had to do with a useless naked function.

It works but it's an ugly hack and I am very much in favour of this RFC.

@eddyb
Copy link
Member

eddyb commented Jul 24, 2016

@parched A "vector table"? Could you not do that with Rust types?

@parched
Copy link

parched commented Jul 24, 2016

@eddyb No, because its just aligned bits of code. So, unless you manually encode the instructions into an array, you can't do it in rust.

@nikomatsakis
Copy link
Contributor

Yes a vector table often has to be done in assembly, which I've had to do with a useless naked function.

Yeah, I consider a "useless naked fn" to be a pretty big hack. I mean, it works, but...

So, this example tilts my opinion to being roughly in favor of this RFC. It seems like a good fundamental capability.

Also, it's worth nothing that when we accepted naked fns, we did so on an explicitly experimental basis. This seems to imply it's worth adding some alternatives (even if they overlap) so that, later, we can assess the ways in which those are being used and try to decide between them. And there are definitely known problems with naked fns (at least those that contain anything other than a single asm! statement).

One question I had:

if you wanted to define a naked fn that could also be called from Rust code, could you do it with a global_asm directive? I imagine you might do it by adding a global_asm for the definition and some sort of extern block for the signature, but I'm not sure if there might be linker complications from that.

@Amanieu
Copy link
Member Author

Amanieu commented Jul 26, 2016

if you wanted to define a naked fn that could also be called from Rust code, could you do it with a global_asm directive? I imagine you might do it by adding a global_asm for the definition and some sort of extern block for the signature, but I'm not sure if there might be linker complications from that.

I've done this before in C, all you need to do is export the symbols from the asm using .global <symbol> and the declare an extern char <symbol>. I expect that this will work similarly in Rust.

@Amanieu
Copy link
Member Author

Amanieu commented Jul 26, 2016

Actually I did do something similar in Rust, except it involved symbols generated by a linker script rather than by asm:

extern "C" {
    static <symbol>: u8;
}

@steveklabnik
Copy link
Member

Yes a vector table often has to be done in assembly, which I've had to do with a useless naked function.

I've done this with a macro in the past, it's not too bad https://github.com/intermezzOS/kernel/blob/master/src/interrupts/src/lib.rs#L16-L76

@Amanieu
Copy link
Member Author

Amanieu commented Jul 26, 2016

@steveklabnik It's a bit trickier on aarch64:

  • The vector table base address needs to be aligned to 2KB.
  • Each entry is 128 bytes long and consists of instructions rather than data. This means that you can place up to 32 instruction in each entry.

@parched
Copy link

parched commented Jul 26, 2016

I looked at my code again, and actually my AArch64 vector table naked function wasn't so useless because I put it in its own section and aligned it in liker script which meant I used the function as the base address of the vector. So it does actually look like a callable function with lots of dead code after the first entry.

Something that would be useful here is exposing LLVM's function alignment attribute, so it doesn't need to placed in its own section and aligned with the linker.

if you wanted to define a naked fn that could also be called from Rust code, could you do it with a global_asm directive?

As @Ericson2314 pointed out, disadvantage of global_asm is the LLVM's module level assembly doesn't have extended functionality. This means if you want to refer to some rust code from assembly, the rust code will have to be #[no_mangle] and (pub or #[allow(dead_code)])

@parched
Copy link

parched commented Jul 26, 2016

Having said that though, rust could easily have is own extended global_asm that accepts compile-time constants and labels. It would evaluate the constants and mangle the labels then substitute them into the module level assembly. This would be really useful and I think would be worth adding to this RFC.

@nikomatsakis
Copy link
Contributor

Huzzah! We discussed this again in the @rust-lang/lang meeting and this time decided to accept this RFC. The general idea is that this is explicitly an experimental feature, similar to naked fns, with the goal of gaining experience in this area so we can have a better idea what our story ought to be.

Some questions that would be good to address in the implementation:

  • what can we do to improve global_asm over importing a .s file?
    • @parched's suggestion of allowing the asm to link against symbols and things might be an example
  • to what extent can we use this feature in place of naked fns? If so, it seems like a more straight-forward option. While it would certainly be possible to support both, it might be nice if we could instead offer only the most fundamental feature (probably global_asm) and perhaps use macros or plugins to build more advanced things on top.

@nikomatsakis
Copy link
Contributor

Tracking issue: rust-lang/rust#35119

If you'd like to keep following the development of this feature, please subscribe to that issue, thanks! :)

@nikomatsakis nikomatsakis merged commit 428446c into rust-lang:master Jul 29, 2016
@Centril Centril added A-macros-libstd Proposals that introduce new standard library macros A-ASM Proposals related to embedding assemly into Rust. labels Nov 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ASM Proposals related to embedding assemly into Rust. A-macros-libstd Proposals that introduce new standard library macros final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.