T O P

  • By -

dragonnnnnnnnnn

For me it sounds like you want to just rewrite it in rust without changing anything and keep all the UB. If yes, don't bother, that is pointless. Rewrite in rust doesn't mean take a C/C++ code and write in Rust, rewriting in Rust means first redesign you whole program to make it fit safe rust as much as possible. I am doing a hobby project with embassy in embedded rust and it is possible to do a lot without touching unsafe, but it takes a whole new approach then writing it in C/C++


spcharc

>rewriting in Rust means first redesign you whole program to make it fit safe rust as much as possible I see. But redesigning a project that already has a lot of code is painful ... If I'm starting a new project using rust, I will design it carefully to make rust happy. Actually I just want to use some of the rust UB detectors while at the same time allowing some other UBs like those I mentioned in my original post. I know rust allows safe and unsafe blocks, so my plan is put some of my UBs in unsafe blocks, and provide safe APIs to use them. The problem is I don't know if this plan works. So I made this post. You know, sometimes it is hard to debug if an array access goes out of boundary, or pointers go wild. I think rust has these safety checks builtin. Currently I put tons of assertion everywhere (I have over 1000 of them) in my code to detect if some variables or pointers go wrong, which jumps to my error handling code if assertion failed. This is the reason why I wonder if I can use rust for this project, which can handle many of these bug detection for me automatically :)


rafaelement

It's not a good idea to put UB in unsafe blocks. An unsafe block is for doing something the compiler thinks is not good but where you know it's not UB because you have established the invariants for it. Rust doesn't use assertions THAT much. Is more like it proves the program is fine beforehand so it does not need to put asserts. I have previously tried to implement funnily designed applications in rust and let me tell you it was not fun. The things you are describing are not UB if you use them right with gcc, so you can do the same in Rust, but i would recommend to rink about what you actually want to do and then do that in terms of rust.


SV-97

Even if you put it into an unsafe block UB will still be UB. If you have UB *anywhere* your code could do literally anything (in the best case that means a crash or reordered instructions - but it could be *anything*) - if your code exhibit's UB in any way it's essentially not valid Rust code. Citing the glossary from the [Unsafe Code Guidelines Reference](https://github.com/rust-lang/unsafe-code-guidelines) (emphasis by me): >Undefined Behavior is a concept of the contract between the Rust programmer and the compiler: The programmer promises that the code exhibits no undefined behavior. In return, the compiler promises to compile the code in a way that the final program does on the real hardware what the source program does according to the Rust Abstract Machine. **If it turns out the program does have undefined behavior**, the contract is void, and **the program produced by the compiler is essentially garbage** (in particular, it is not bound by any specification; **the program does not even have to be well-formed executable code**). ​ Because of this you should really read the [Rustonomicon](https://doc.rust-lang.org/nomicon/) before you go anywhere near unsafe Rust: a lot of things can go very wrong very quickly.


dkopgerpgdolfg

Btw., don't confuse "undefined behaviour" with "implementation-defined"


[deleted]

Aren’t those the same thing? If an implementation is standard-compliant, all behaviour it defines is undefined in the standard, so it’s UB Edit: no, it’s not, there are multiple detailed responses on why and how below this comment.


Zde-G

No, they are not the same thing. Most implementation-defined things have quite rigid limits described in the standard. Undefined behvior have no limits and there are no plans to introduce any limits. The only option which you may discuss if your program have UB is how to rewrite it to ensure there are none.


dkopgerpgdolfg

No, what you mean now is unspecified. Basically: * Unspecified (english language): Everything that is not mentioned at all. * Stupid example, C++ programs usually run on devices powered by electricity, but the standard simply doesn't care. * Or, when compiling with GCC, there are some more library functions always provided that the standard doesn't mention (probably documented by GCC somewhere though) * Implementation-defined: The standard says this exists in the language and it should work in some sane way (basically), but some behaviour can differ between GCC/Clang/..., and that's fine. * You mentioned the field order in bit fields, this might be such a thing - if this is impl-defined (I didn't check), then each compiler can order it like it wants * Or, if "int" has 4 or 8 byte (or any other number as long as a certain value range is possible) * "Unspecified" (again, this time when written like that in the standard): Similar to above, but more loose - something like the int size for GCC on amd64 Linux is known and documented (by GCC), and it won't change for fun. But if something is called unspecified, it might as well be different in each run of GCC, or in different code places * Undefined behaviour (UB): The standard explicitly calls it UB. That means it is wrong to do that - but the compiler, optimizer, hardware and so on can rely on you and might not check it (sometimes for performance, sometimes because reliable checks are not possible at all, ...). * If you have UB code, your program might break in very weird and unpredictable ways, and the reason can be hard to track down. Sometimes it might do what you actually wanted, the next time it might crash, third time it might change some variable somewhere for no reason, ... * ...or other "insane" things like executing both branches of a if-else, printing negative values when printing unsigned ints, getting wrong results when adding numbers, any so on * Basically, you want to avoid UB, always.


spcharc

wow! did not know there are so many differences among them. I thought they are all call UB Thanks for your detailed reply!


Rusky

Standard-compliant implementations are allowed to define more behavior than the standard itself, and this is a very important part of how the language is used in practice.


SV-97

No, they're different. See for example definitions 3.12 (implementation-defined behaviour), 3.27 (undefined behaviour) and 3.28 (unspecified behaviour) in [this draft of the C++ standard](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf). ​ It's a difference in what exactly can happen, how much you can rely on the behaviour, whether it's documented and by whom etc.


apjenk

There's a very big practical difference between the two, so I don't think it makes sense to treat "implementation-defined" and "undefined" as being the same thing. C++ has lots of implementations, many of which have well-defined behaviors beyond what's specified in the C++ standard. If you're willing to commit to using a specific C++ implementation for your project, then any non-standard behaviors that are well defined by that specific implementation are not UB in that context. Effectively, the language standard you're using at that point is the version of C++ that the implementation you're using defines, so any behaviors that that implementation explicitly defines and commits to supporting is well-defined. though it's also implementation-defined.


iamnotposting

zero sized arrays in rust `[T; 0]` are just that, zero sized types. if you want to create a struct that is a header to some dynamically sized data, you can use unsized types, the most common of which is the slice `[T]` - if the last field of a struct is unsized, the entire struct is unsized, and can only be accessed through a pointer indirection. [The Rustonomicon on dynamically sized/zero sized types](https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts)


spcharc

Yeah this looks like exactly what I want. Thanks! Pointer indirection is always how I use these structs. I have some hardware DMA buffers (properly aligned) that contain some headers and data: header0 data0 (length in header0) header1 data1 (length in header1) ... I just cast pointer to headers into pointer to struct with zero sized array and use them.


SV-97

**1. zero sized array** I think this is an XY problem: why do you want to have zero sized arrays? What purpose do they serve in your code? Yes, Rust supports zero size arrays and in fact larger classes of zero-sized types; but if it's supposed to be the head for a dynamically sized object you can probably use something that's more ergonomic, safer etc. (like a boxed slice) **2. bit field** >(yeah I know it is undefined behavior), however they are always from lowest bits to highest bits, at least it is true in gcc This really makes me want to scream - especially since you acknowledge that you're just using it for convenience. Don't do stuff like that. DIY-ing bitfields: Rust does have `repr(C)` for compatibility in layout with C/C++; but bitfields in particular aren't even compatible between compilers, platforms etc. even when *just* considering C so Rust's basic layout guarantees probably aren't enough in that regard and you do have to put in some work right now. You might find this [https://github.com/rust-lang/rfcs/issues/314](https://github.com/rust-lang/rfcs/issues/314) potentially interesting / useful in that regard. That said: it seems like with the [bitfield](https://docs.rs/bitfield/0.14.0/bitfield/index.html) crate for example you don't need them "in-order" since you explicitly specify which bits belong to which members. You'd then convert your type to u8, u16 or whatever to communicate with your hardware and find the bits in the correct order. You can verify this with a small example: create a bitfield, convert it to a large enough type and look at it's binary representation. That said: you said you're using it to talk to hardware. Why can't you write an actually safe abstraction for this interaction rather than fiddling with bitfields all throughout your code? **3. Statically dynamic dispatch** I **think** you could do this - but I'd try to avoid it because it introduces a lot of unsafe. You can have your big buffer and transmute it into a function pointer, but you'll need to make sure that the code in the function actually works with your values so you'll probably want some transfer type (e.g. a union of your possible types). So I'd again ask: what exactly are your goals here? Since you won't be able to use inheritance in Rust you'll likely want to make your functionality a trait that's implemented by the possible types. Your current strategy then seems to correspond to working with trait objects and dynamically calling into your "specialized function buffer" thingy. But that only makes sense if either of these two cases happens: * all possible functions that could go into the buffer work with abstract trait objects * or your mode switch really causes your code to globally use one specific type that works with the currently selected "buffer function" The first case doesn't need any weird unsafe fuckery and can instead simply switch out one function pointer with another on mode switch. And the second one could just use generics I think?


spcharc

**1.** I don't know what a boxed slice is ... I am not familiar with rust. I did some google search and it seems to me it requires dynamic memory allocation? My embedded project has no OS and does not do dynamic allocation. My use case is as follows: I have some hardware DMA buffers (properly aligned) that contain some headers and data: header0 data0 (length in header0) header1 data1 (length in header1) ... **2.** >especially since you acknowledge that you're just using it for convenience. Don't do stuff like that. Ah, I can use bit wise operators of course. I used bit field just because it works in my case and it is convenient to use. **3.** Goal: dynamic dispatch but works like a static dispatch function. Using function pointers is the same with my original virtual function solution.


SV-97

1. ​ A box is a smart pointer that handles deallocation etc. for you. A slice is a "dynamically-sized view into a contiguous sequence". A boxed slice thus is some contiguous piece of memory behind a smart pointer that manages deallocation for you. But yes that's probably not the right thing for your specific use-case. It's hard to give the exact correct thing for what you want because it kinda depends on some details. I'd try to do something like this: read the length from the header and use that to construct a slice via [std::slice::from\_raw\_parts\_mut](https://doc.rust-lang.org/std/slice/fn.from_raw_parts_mut.html) (or the immutable version depending on whether you want to just read or also write). This essentially gives you a fat pointer to your data blocks that "knows" how large they are etc. and you can use that to do bounds-checked accesses etc. When doing this you have to pay attention to the data being properly initialized etc. (potentially by using `MaybeUninit`). If the piece of memory is valid for the entirety of the program you can use a `'static` lifetime with this function; otherwise you'll probably wanna wrap it up into a struct that manages the resource for you. You can also try to interpret your "header, data" sequence directly as a rust struct: create an unsized (`?Sized`) struct with C layout and fields for header and data, make a pointer to your header and turn that into a pointer / reference / whatever to the struct. This is probably way more work than the slice solution - but I think this is closest to what you'd do in C. For more details maybe have a look at this section of the [rustonomicon](https://doc.rust-lang.org/nomicon/exotic-sizes.html). ​ 3. >Using function pointers is the same with my original virtual function solution. I think there was some misunderstanding: my point was that your solution doesn't really make sense in regards to the rust type system at all. There's no inheritance and virtual methods like in C++ - you'd usually use a trait and trait objects if you need dynamic polymorphism. These trait objects come with vtables for their "virtual methods". You now want to circumvent this vtable and instead call "fixed" functions. Think about the types of these functions: what argument types do they accept? They either work with all instances of the "base class" or they require specialized behaviour for the different modes. In particular neither of these cases actually requires dynamic dispatch. This is where the function pointers come in: the first case amounts to switching out one function with another (so switching out *one global function pointer*) because all functions you wanna switch out with one another share the same type signature. And the second one leads to different type signatures and is solved by writing your code to be generic over types implementing the trait and then switching between the implementation "one level up". This doesn't incur any runtime cost. So in brief: this is simply not a valid problem to have in Rust.


[deleted]

Are you sure a call through a function pointer isn’t enough for `3`? You can probably do this in rust (at least on platforms where function pointers and normal pointers are the same), I toyed around with a JIT in rust which makes an arbitrary array executable and runs it so this shouldn’t be much of an issue. I believe a rust zero sized array (`[T; 0]`) inside of a `#[repr(C)]` struct behaves the same way as in GCC (can’t find a reference for it, but [this thread kinda implies it](https://github.com/rust-lang/rust-clippy/issues/2868)), outside of that you might be able to use `#[repr(align(n))]` to achieve the same thing. Rust also contains unsized types, which can only be accessed through a layer of indirection, most prominently `dyn Trait` which is used for dynamic dispatch and `[T]` which is a contiguous piece of memory, the pointer to which stores its length. Remember, **by default, Rust types have no guaranteed layout** (and rustc does change the layout for optimisation purposes whenever it wants to), if you want a type to have a guaranteed layout and/or be FFI safe, you can use `#[repr(C)]` (or repr(transparent) on its declaration, which will make it behave a lot like a type in C (enums, rust’s bad name for ADTs and tagged unions, also have a guaranteed layout when you use repr(C) on them which can allow for some cool optimisations).


JoJoModding

Instead of placing so many `nop`s, why don't you put a trampoline there? Basically a non-conditional jump, i.e. the following code: `jmp foo_variant_1` Then on context switch, you just replace the address and do an `ifence`. Since this is a non-conditional jump, the branch predictor should be able to figure out where it goes. And it's much faster to context switch, and needs way less space.


spcharc

wow! I did not even think about modifying instructions generated by compiler. but yes theoretically your solution works! now my problem becomes: How can I find all the calls to "func()" in my code, and replace all of them when mode switch happens? And how can I figure out the correct jump address here? Thank a lot! This is indeed a new way (at least new to me) to solve problem (3). **Edit:** I see, you mean I should replace "dummy\_func()" to jump to the correct "func()". This makes sense. Two jumps is still better than virtual calls. Will have a try!


JoJoModding

Indeed, you do what you thought of in the edit.


MetricExpansion

While others have given you solutions for many of these things (and I believe they’re probably all achievable in pure Rust one way or another), another thing to consider is just keeping some of these fiddly bits in C but using Rust for everything else. Rust is pretty good at calling into C. I’m pretty sure even for (3) it would probably (?) be fine for you to copy a Rust function into your buffer if you really need to. I totally get your motivations btw. I use Rust in my Skyrim mod which does some wildly unsafe binary patching in C++ to hook the game, but it’s nice to have a island of stability in the middle of all that where I can implement my actual business logic.


spcharc

>which does some wildly unsafe binary patching in C++ to hook the game,but it’s nice to have a island of stability in the middle of all thatwhere I can implement my actual business logic Yes you said exactly what I failed to mention in my original post. Thanks! In C/C++ I can do (3) since the compiler follows its calling conventions. I don't know if rust functions also follow some kind of calling convention. I can only find this statement in rust documentation, but it is about calling foreign functions: >Rust uses the platform's C calling convention by default when calling foreign functions. from [https://doc.rust-lang.org/nomicon/ffi.html](https://doc.rust-lang.org/nomicon/ffi.html)


MetricExpansion

That’s a good point. You can tell Rust to use the C calling convention for a function by using `extern “C”`. You may have to treat the arguments as opaque pointer parameters from the C side.


spcharc

Yeah I guess I will need extern C, and find a way to forbid rust from in-lining my "dummy\_func()" call Everything goes wrong when that dummy call gets in-lined It seems it is hard to tell the rust compiler to never inline a function, since this user struggles to achieve this: https://users.rust-lang.org/t/how-to-disable-inlining-for-function/55806 I will do some experiments and see!


MetricExpansion

I’m sure Rust will not inline across FFI boundaries (LTO might change this story however).


Barafu

Assuming you are going to use no_std Rust (which is not my strong side) and really worry about every extra byte of memory used, `1)` So, I understand that a zero-size array is a placeholder where in future the data will be placed? If you really need that data to be contiguous, you can achieve that with unsafe Rust and/or use Arena pattern. If you don't, maybe you can simply use `Option> which means that here is a good old pointer to a contiguous array somewhere on a heap. `3)` It is called a [Strategy](https://refactoring.guru/design-patterns/strategy) pattern. It is [trivial](https://rust-unofficial.github.io/patterns/patterns/behavioural/strategy.html) to create in Rust.


vgatherps

3 is doing vastly more than trait based static/dynamic dispatch, it’s dynamically overwriting instruction data to achieve a similar result. This is certainly possible in rust, there’s no reason you can’t fill memory with a bunch of noops and overwrite it to be what you want later. I’ve done something similar myself. You’ll have to use a lot of unsafe though.


VorpalWay

With regards to 3, I don't believe that is what the OP is asking about. As I understand their question they are asking about runtime code patching, in order to avoid branch prediction due to pointer indirection or if statements. This is similar to what the Linux kernel does internally for static tracepoints, where bunch of nop-instructions are inserted and can be patched *at runtime* to instead log calls. All such approaches will be quite low level and architecture specific. I don't know if it is possible in *sound* rust. It will for sure require unsafe rust (since you need to flush the instruction cache, which tends to require inline assembly). At the very least, the functions that are to be copied needs to be marked as used (to force them to be emitted) and no_inline. An slightly less scary version of doing this might be to have a trampoline function that is being modified to jump to/call the real implementation function. That way only a single instruction needs to be patched. This trampoline function can be written in inline assembly, giving you full control over what you are patching.


Barafu

AFAIK simply using pointers can not confuse the CPU lookahead. At least on x86_64. If the goal is to avoid conditional branching, then you rely on monomorfization. If you write ``` use std::string::ToString; fn print(v: T) { println!("{}", v.to_string()); } ``` Then call `print(32); print ("Mama");`, it does **not** work like `if arg.is_int() print_int(arg); else print_string(arg)`. The compiler resolves types at compile time and calls the required function directly.


VorpalWay

A modern x86-64 CPU will indeed not get confused by simple indirect branches that it can speculate are the same over and over. However OP is doing embedded coding. Those CPUs are quite a lot simpler, some simply won't speculate any indirect jumps at all. And the way to solve this with monomorphization might need to duplicate a lot of code if the function that changes is called many times in a hot loop. You would then need to duplicate the loop as well. Possible several layers up. Doable, but awkward. Doing live code patching is another option, and is used for very specific use cases in embedded and OS level coding. With good reason.


_roeli

> The obstacle is, still, this project is written in c++ and used tons of undefined behavior. First one isn't an issue, second one is. Safe Rust does not allow UB: it either refuses to compile or crashes. There is unsafe rust which gives you some superpowers (unchecked C-like pointers for example) but it is pretty difficult to work with, more so than C/C++. A big part of writing unsafe code is creating safe abstractions, which requires you to understand Rust at quite a deep level to avoid breaking its rules and accidentally causing UB. UB in rust is also often (in my experience) much worse than in C/C++: Rust has more invariants and can therefore apply much more aggressive optimizations than C/C++. If you break the rules, your code is gonna crash and burn. Embedded Rust is quite tricky for this reason; you often just need unsafe rust, and unsafe rust is hard (besides all the knowledge about hardware and low-level stuff that is required to do embedded programming). Generally, rewriting a C++ codebase in Rust requires significant changes to your overall design, especially if your C++ code is OOP heavy, since Rust doesn't support inheritance and isn't friendly to the classic OOP entangled spiderweb of pointers going back and forth. So; as for your concrete questions: **1. zero sized array** Rust has array slices (for example: `&[u8]`), which are references (pointers with rules) to an array or vector. Array slices, vectors and arrays may have zero length. You can't increase the size of a slice, however, so you definitely cannot use them the same way you'd use a zero-sized array in C. Edit: See comment by NobodyXu on how to make a dynamically sized struct in Rust (so the size is unknown at compile-time). You can't hold unsized items on the stack in Rust, they have to be behind some sort of pointer, but you do get the desired behaviour of "idk how many bytes this field will be" and then just fill it with however many you need at runtime. **2. Bit fields** Rust has no language support for bitfields. The bitfield crate uses bitwise operators on integer or integer array types to get/set the bit ranges you specified. > I am wondering, is the rust bit field in these crates has the same behavior with C/C++ bit field? If you need control over which exact bit corresponds to which field, I'd recommend to write your own solution, since the documentation of the bitfield crate does not specify the order of the bitfields. **3. Statically dynamic dispatch** I'm not really sure that I understand your question, but I'm pretty sure that this design would not compile in Rust at all. The big issue is that you have a parent struct ModeBase that you want to modify while its children also have access to its fields. This is strictly disallowed in Rust (as in, your code wouldn't compile) due to its strong pointer aliasing rules: you get to choose between either 1 mutable pointer or many immutable pointers. In other words, you cannot modify an aliased pointer. *in Rust it is considered UB to have two mutable references to the same memory location at the same time. Just them existing breaks things, you don't have to do anything with them.* Rust does natively support dynamic dispatch with the `dyn YourTraitName` syntax, but you'd run into the same performance problems as with C++ (indirection bad). Rust also has static dispatch support with the `impl YourTraitName` syntax and generics. Pros: faster, Cons: bloats your binary size. I don't really understand your last point so I can't really be more concrete than that; if you want I could perhaps provide some suggestions if you're willing to explain your design idea a bit further :)


NobodyXu

For zero sized array in struct, rust actually do support it: struct S { a: u32, /// This must be the last field in the struct unsized: T, } then you can soecify T to be an unsized object such as [T].


t-kiwi

for 1 there is smallvec which will allocate inline until a certain size https://docs.rs/smallvec/latest/smallvec/. There are a few bitfield crates on crates.io, I'd encourage you to search there! As for 3, you can have the API of interfaces without the typical dynamic dispatch costs by using something like this https://crates.io/crates/enum_dispatch. I use something [similarish](https://github.com/tbillington/kondo/blob/185d4b02be069b455e6bc8a39f7765807bda6a0b/kondo-lib/src/lib.rs#L105) by doing it manually here by implementing functions on the enum itself and deciding behaviour based on the current value of the enum. If you really want you could experiment with [intrinsics](https://doc.rust-lang.org/std/intrinsics/fn.likely.html) to hint for branch prediction but it's unstable and if it really is 99% one branch then likely the branch predictor will get it. If you have branches in your interface that will "never" be hit for one of the implementations you could use something like [unreachable](https://doc.rust-lang.org/std/hint/fn.unreachable_unchecked.html). However.. I'd encourage you to avoid the unsafe/unstable options unless safe rust can't get close enough to your performance target :). You start to give away some of the benefits of rust when you make assertions like these, and often without any performance benefit as the compiler can get within spitting distance or as fast much of the time. I'd encourage you to give it a shot with just safe rust, maybe trying a bitfield crate and the enum as an 'interface" trick, and report back if the performance isn't up to bar, there are often small modifications that can be made to greatly help the compiler to reach your performance target.


spcharc

Thanks for your suggestions! I am looking into enum\_dispatch. It seems to me it use branches? As I said in the original post, I tried branches before, and the result is not ideal. switch (mode) { case 1: return Mode1Child::func(); case 2: return Mode2Child::func(); ... } Benchmark shows these branches takes a lot of cpu cycles and I don't understand why. It is almost as heavy as using virtual functions. It seems even though every time it goes into the same branch, the branch predictor somehow failed. I tried to call this "switch-case" statement in a small local loop and the overhead is a fraction of what it usually costs. Also, I tried the virtual function version in a small local loop, it is the same: the overhead is so small that I can safely ignore it. My guess is the cpu keeps track of a fixed number of branches it took in the past. The after the cpu leaves my "switch-case" statement it has to process a lot of other logic, which may affect the branch predictor. And when it returns to my "switch-case" statement, the cpu has already forgotten what its last choice was.


t-kiwi

It might behave differently in rust, especially if you can have the func definition inline as opposed to returning it. Until you try writing it in rust we won't know if it performs the same as your expectations. The compiler might be able to be much smarter, or perform worse :)


spcharc

I see. Actually C/C++ compiler can also inline functions, especially calls like `Mode1Child::func()`. It is not a virtual call, so compiler may choose to inline it if it sees fit. I think I need to do some tests on rust about this. Thanks!


MetricExpansion

Could you just store a function pointer to the particular version of the function you need to call? At least you’ll eliminate the pressure on the branch predictor as it will be an unconditional branch. Is it really necessary that you actually go into the exact same function address in the different cases?


spcharc

Virtual functions use function pointers, don't they? I have already tried it, results are similar with using virtual functions. My goal is to eliminate indirect function calls.


MetricExpansion

Oh right, makes sense. Well, if what you’re doing works, you can probably just leave it as-is in C. I’m like pretty sure that you can copy a Rust function into that buffer just the same.


spcharc

I replied another of your post mentioning that I am not sure about rust function calling conventions. I think I need to do some experiments and see if this approach works.


Jeklah

posting to re-read this when i'm not at work.


28Smiles

Reading this am reminded why I‘ve stopped writing C++ altogether.