Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
You can pass &a.i down as an int*, or in C++ you can pass an A* (or A&) and an int A::* member pointer.

Adbot
ADBOT LOVES YOU

Beef
Jul 26, 2004

Xarn posted:

No. In theory "actually const objects" cannot be changed on the pain of UB, but when has that ever stopped programmers :v:

code:
struct foo;
void call(const struct foo);
{ // somewhere in main
  struct foo obj;
  call(obj);
}
The object copied to the frame is const, but not the original on the call site. If obj gets copied, any thread can change it all it wants while 'call()' is executing. roomfortuna is right in that the compiler needs to be able to prove that no thread will be able to change 'obj' in order to elide the copy.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

Azuth0667 posted:

Similar, I have:

code:
struct a{
     int x;
     int y;
     int z;
};

void ChangeLetter(struct a i, char v, int num){
     i->v = num;
}
I'd like to be able to pass i->v directly into the function and change it by num. I don't think I can do that though from what I've been reading.

You should be able to pass in a pointer to the struct element and modify it in your function.

e: https://repl.it/repls/VictoriousGummySandbox

taqueso fucked around with this message at 17:09 on Jun 27, 2019

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
How does strict aliasing interact with multithreading in this context? Would another thread modifying that struct violate strict aliasing, or does that only refer to aliasing within the same thread? If you tell the compiler you aren't using threads, and using static functions in the same compilation unit, will it do the optimization?

Beef
Jul 26, 2004
I hope so. *sacrifices a chicken to D'agonbhookus, god of optimizing compilers*

If the original object is passed from somewhere else as a pointer, then making it a restrict pointer would indeed help to tell the compiler no one else should be changing it while you're handling it. In this case, the object was plainly on the stack, so it's not an aliasing problem.
Forcing a copy elision by taking the address of the source object and slapping on a const restrict const pointer would be some next level poo poo.

The fundamental issue is that threading defined as a library, it is a lovely hacky library completely opaque to the C language and compiler. If you want to do multithreading for performance reasons, avoid posix threads. OpenMP can offer some extra information to the compiler (e.g. objects in parallel scope are automatically private, outside are shared; loops are assumed canonical parallel form; etc.)

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Threading isn't really the issue. The C abstract machine says that the parameter variable is its own object, which implies three things: (1) modifications to wherever the argument came from can't be observed in the parameter, (2) modifications to the parameter can't be observed in wherever the argument came from, and (3) the address of the parameter must compare different from that of any other object. Assuming that the argument is an l-value naming some existing object, this amounts to requiring a copy by default; in order to elide this copy, the compiler must prove that those three rules won't be violated and, additionally, must use a calling convention that doesn't implicitly copy the argument anyway.

Let's deal with the last point first. There are two ways in which a calling convention can force a copy: it can pass the contents of the parameter in registers, or it can pass the parameter in the stack argument area. The former generally only triggers for very small types, which is presumably not a significant source of overhead. The latter doesn't actually require copying, but managing multiple argument areas at once in a function is very annoying for compilers, so the compiler is much more likely to emit the argument into a temporary and just copy that into the argument area immediately before the call. The calling convention you're hoping for if you care about eliding copies is that the compiler passes the argument implicitly by reference; generally the convention here is that this reference becomes the parameter and therefore must not be aliased in the caller. In theory the compiler can elect to use this convention even if it's not the normal ABI rule, but rewriting calling conventions is a tricky optimization that requires total understanding of how the function is used (or introducing thunks, which aren't necessarily good for performance) and moreover doesn't usually interact well with debuggers.

Proving that modifications can't be observed generally means proving that neither object is modified during the call. Technically there's a stronger condition — you can allow a modification to one object if that portion of the other object will never thereafter be observed — but it's much more annoying to reason about and it's unlikely to catch much more in practice. The easiest way to prove this is just by examining all the uses of the variable and showing that none modifies it; if a pointer to the variable escapes, you conservatively assume it can be modified that way. Also, as Xarn said, if the variable is declared const then you have an additional guarantee (on penalty of UB) that it won't be modified, even if a pointer to the variable does escape; however, it's important to understand that whether a parameter is const is not part of the signature of a function, and there is nothing in C which allows you as a programmer to guarantee that a parameter won't be modified, so this is still purely function-local information.

Proving that using the same address for two "different" objects doesn't matter generally comes down to proving that the address is never observed for at least one of the objects. Again, technically there's a stronger condition possible, but this is easier. The important thing here is that you only have to prove it for one of the objects (as long as you apply the analysis iteratively — no fair deciding that A's address is never observed and so A can be unified with both B and C, whose addresses are both observed). Escapes are totally fatal to this analysis.

The upshot of all this is that there's basically one situation where you can do this optimization based purely on a single function's information: if the function passes an object as an argument, hasn't let the object's address escape, and doesn't observe the value of the object after the call. Otherwise this has to be an interprocedural optimization, which means (again, probably) devirtualizing the call and doing a complicated analysis on both sides. So that's why it's not a common optimization.

Threading doesn't really complicate this because any situation in which another thread could modify one of the objects is a situation where the object's address has escaped. Once the address has escaped, even without threads, you have to assume that any arbitrary call might cause it to be modified. Threading just means that this also applies to any read barriers you can see.

rjmccall fucked around with this message at 18:54 on Jun 27, 2019

Beef
Jul 26, 2004
Holy poo poo, thanks for the amazing write-up. This is a bazillion times clearer than what I got back from our own compiler team.

Why can't the value be observed after the call? I assume it's because, as you just mentioned, const is not part of the signature. But surely the compiler has that information when it all happens inside the same translation unit.

double post prevention edit: In moments like these, I really miss working in D: https://dlang.org/spec/const3.html

Beef fucked around with this message at 21:43 on Jun 27, 2019

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Beef posted:

Why can't the value be observed after the call? I assume it's because, as you just mentioned, const is not part of the signature. But surely the compiler has that information when it all happens inside the same translation unit.

Right: if it's a direct call within the translation unit, you can do the interprocedural optimization to see if the callee modifies its parameter, and that analysis might be really easy if you can just look and see that the parameter is const. That's still using information from multiple functions, though, which means you have to think about things like the order in which you optimize functions. (Maybe the callee doesn't modify its parameter because it copies it when making a call of its own! You could get different optimization result depending on which function you applied the optimization to first.)

big black turnout
Jan 13, 2009



Fallen Rib
Is there a good imgui tutorial? The docs (or lack thereof) suck

Volguus
Mar 3, 2009

big black turnout posted:

Is there a good imgui tutorial?

Yes: imgui_demo.cpp

qsvui
Aug 23, 2003
some crazy thing
I just came across some production code that uses RTTI when std::is_same would have done the same thing. Which made me wonder, does anyone actually use RTTI?

Absurd Alhazred
Mar 27, 2010

by Athanatos

qsvui posted:

I just came across some production code that uses RTTI when std::is_same would have done the same thing. Which made me wonder, does anyone actually use RTTI?

std::is_same only works if the types are known at compile time, doesn't it?

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

qsvui posted:

I just came across some production code that uses RTTI when std::is_same would have done the same thing. Which made me wonder, does anyone actually use RTTI?

Yes, people do use dynamic_cast and typeid.

Xarn
Jun 26, 2015
What's even worse, some people use reinterpret_cast :suicide:

Absurd Alhazred
Mar 27, 2010

by Athanatos

Xarn posted:

What's even worse, some people use reinterpret_cast :suicide:

You kind of have to when you're doing callbacks from C-based libraries, unless they have something like COM going on.

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
Or getting binary data from like, any source other than your software?

qsvui
Aug 23, 2003
some crazy thing

Absurd Alhazred posted:

std::is_same only works if the types are known at compile time, doesn't it?

Yes, in this case the typeid of a template parameter was being compared with the typeid of hard coded types :shrug:

Absurd Alhazred
Mar 27, 2010

by Athanatos

qsvui posted:

Yes, in this case the typeid of a template parameter was being compared with the typeid of hard coded types :shrug:

I don't understand what you're trying to say. If you get an object types as a pointer/ref to its ancestor, and are looking to find out which of the descendants it actually is in order to decide which version of its methods to call, even if you get that by checking through a list of typeids, you're still going to need to use dynamic_cast to get a proper pointer/reference to have the virtual table interpreted correctly, right? reinterpret_cast will cause undefined behavior. And you're going to want to give your objects at least a virtual destructor if you want to use the ancestor as the type of containers, otherwise you're going to get memory leaks. You could build a workaround where the only other virtual method in the ancestor is something like this() which gives you its own version of the pointer and lets you reinterpret-cast the rest and then don't call any virtual methods, but what exactly are you really gaining over just using RTTI?

Xarn
Jun 26, 2015
I think the point is that this

C++ code:
template <typename T>
void fart(T const& butt) {
    int intensity = 1;
    if (typeid(T) == typeid(WeakButt)) {
        intensity *= 2;
    }
    butt.fart(intensity);
}
is stupid, because you could handle intensity of the farts at compile time.

Xarn
Jun 26, 2015

Jeffrey of YOSPOS posted:

Or getting binary data from like, any source other than your software?

I am willing to bet that over half uses of reinterpret_cast in the wild are deep in the UB territory. (Use memset)

Absurd Alhazred
Mar 27, 2010

by Athanatos

Xarn posted:

I think the point is that this

C++ code:
template <typename T>
void fart(T const& butt) {
    int intensity = 1;
    if (typeid(T) == typeid(WeakButt)) {
        intensity *= 2;
    }
    butt.fart(intensity);
}
is stupid, because you could handle intensity of the farts at compile time.

This code is handling it at compile time.

Volte
Oct 4, 2004

woosh woosh
I put that code into godbolt.org to see what exactly I'd have to do to make clang actually put a runtime typeid lookup in there, and it turns out that you have to not only change typeid(T) to typeid(butt), but you also have to make sure that there are at least two different code paths to the same function call where the compiler can't deduce the actual type of the argument. I did it by instantiating either WeakButt or StrongButt (both subclasses of virtual base class Butt) based on the result of a call to rand(). Even just doing Butt *butt = new WeakButt(); and passing in butt still allowed it to compute the typeid as being WeakButt at compile time.

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS

Xarn posted:

I am willing to bet that over half uses of reinterpret_cast in the wild are deep in the UB territory. (Use memset)
look if you send me a struct on the wire I'm using a struct and you ain't gonna stop me - the compiler is my friend and it does what I tell it!!!

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

I didn’t painstakingly compute the alignments just to pay for an extra memory copy, buster.

qsvui
Aug 23, 2003
some crazy thing

Absurd Alhazred posted:

This code is handling it at compile time.

Volte posted:

I put that code into godbolt.org to see what exactly I'd have to do to make clang actually put a runtime typeid lookup in there, and it turns out that you have to not only change typeid(T) to typeid(butt), but you also have to make sure that there are at least two different code paths to the same function call where the compiler can't deduce the actual type of the argument. I did it by instantiating either WeakButt or StrongButt (both subclasses of virtual base class Butt) based on the result of a call to rand(). Even just doing Butt *butt = new WeakButt(); and passing in butt still allowed it to compute the typeid as being WeakButt at compile time.

For this simple example, I can see that typeinfo is generated if you disable optimizations. I know it's a bit of a cop out to specify that but I think we'd all rather use constructs that ensure compile time handling:

C++ code:
template <typename T>
void fart(T const& butt) {
    butt.fart(1);
}

void fart(WeakButt const& butt) {
    butt.fart(2);
}

Absurd Alhazred
Mar 27, 2010

by Athanatos
Yes, in some cases you can resolve things at compile-time, and then it's best to do that through templates. Definitely don't use RTTI then. :shrug:

qsvui
Aug 23, 2003
some crazy thing

Jeffrey of YOSPOS posted:

look if you send me a struct on the wire I'm using a struct and you ain't gonna stop me - the compiler is my friend and it does what I tell it!!!

use std::bit_cast!!! (when it comes out)

qsvui fucked around with this message at 19:11 on Jul 13, 2019

Xarn
Jun 26, 2015

Absurd Alhazred posted:

This code is handling it at compile time.

Not with MSVC, it does not https://godbolt.org/z/w4gJ7f

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
If you want safe binary deserialization without copying your entire object, you can use the flyweight pattern, i.e. an object that wraps a pointer to your buffer and has accessors to pull out individual fields. That's how most encoding scheme tooling like flatbuffers or capnp work. Downside is lots of boilerplate if you're not using tools to generate it for you, upside is it's defined behavior and you can lay things out on the wire differently than a C++ struct.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Xarn posted:

Not with MSVC, it does not https://godbolt.org/z/w4gJ7f

Okay, so it can fail to do this. Fine, don't do whatever this is about. It's a weirdly constructed situation, though, and actually uses the alternative qsvui was suggesting that's better than RTTI. Honestly, it isn't not RTTI, you're just pushing RTTI to comparisons of typeids. :shrug:

Edit: I can understand why a compiler would look at this piece of code and think "well, I don't know enough about typeid locally to make really wide-ranging decisions, what if StrongButt someday has a descendant with typeid equaling WeakButt's, can't be too careful! Shows a certain improvement of clang's -O2 vs MSVC's /O2 that it might have a better semantic understanding of the language.

Absurd Alhazred fucked around with this message at 19:44 on Jul 13, 2019

Star War Sex Parrot
Oct 2, 2003

Subjunctive posted:

I didn’t painstakingly compute the alignments just to pay for an extra memory copy, buster.
Seriously.

Xarn
Jun 26, 2015

Absurd Alhazred posted:

Okay, so it can fail to do this. Fine, don't do whatever this is about. It's a weirdly constructed situation, though, and actually uses the alternative qsvui was suggesting that's better than RTTI. Honestly, it isn't not RTTI, you're just pushing RTTI to comparisons of typeids. :shrug:

wat? These are the original two posts

qsvui posted:

I just came across some production code that uses RTTI when std::is_same would have done the same thing. Which made me wonder, does anyone actually use RTTI?

qsvui posted:

Yes, in this case the typeid of a template parameter was being compared with the typeid of hard coded types :shrug:


The whole point is that the code I wrote and then linked on godbolt is the COMPLETELY WRONG way to solve this situation. If you have a limited set of hardcoded types in a template, you use std::is_same which works completely differently from rtti based comparisons, such as typeid.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

Xarn posted:

I think the point is that this

C++ code:
template <typename T>
void fart(T const& butt) {
    int intensity = 1;
    if (typeid(T) == typeid(WeakButt)) {
        intensity *= 2;
    }
    butt.fart(intensity);
}
is stupid, because you could handle intensity of the farts at compile time.

std::is_same is not semantically equivalent to this piece of code. T may not be the most-derived type of butt; if butt's runtime type is a subclass of T then is_same would give true while the typeid check gives false.

If that isn't the desired behavior then the problem with the code is just that it's wrong, not that it's doing a pointless runtime check.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Xarn posted:

wat? These are the original two posts




The whole point is that the code I wrote and then linked on godbolt is the COMPLETELY WRONG way to solve this situation. If you have a limited set of hardcoded types in a template, you use std::is_same which works completely differently from rtti based comparisons, such as typeid.

Fair enough, then I'm going to repeat that just don't use RTTI if compile time does work, but sometimes you do need RTTI because sometimes you don't know all the type information at runtime. Unless qsvui thinks you should always design assuming type information will be absolutely known at compile time.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Plorkyeran posted:

std::is_same is not semantically equivalent to this piece of code. T may not be the most-derived type of butt; if butt's runtime type is a subclass of T then is_same would give true while the typeid check gives false.

If that isn't the desired behavior then the problem with the code is just that it's wrong, not that it's doing a pointless runtime check.

This code is actually comparing static types, though, so it should be equivalent to std::is_same. If the code were using typeid(butt) then you'd be right.

Adhemar
Jan 21, 2004

Kellner, da ist ein scheussliches Biest in meiner Suppe.

Jeffrey of YOSPOS posted:

look if you send me a struct on the wire I'm using a struct and you ain't gonna stop me - the compiler is my friend and it does what I tell it!!!

This is routinely done in the HFT space, but it’s possible because the exchange protocol spec makes strong guarantees about how the wire data is laid out. It’s just a matter of writing/generating your structs to match.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

RDMA has been a thing for decades, and nobody was copying out of those pages if they didn’t have to

Malcolm XML
Aug 8, 2009

I always knew it would end like this.
Yeah it's a drat shame c++ has never made doing that very common operation easier.

Let's gently caress around and get graphics into the stl instead.

Absurd Alhazred
Mar 27, 2010

by Athanatos

rjmccall posted:

This code is actually comparing static types, though, so it should be equivalent to std::is_same. If the code were using typeid(butt) then you'd be right.

Okay, yeah, so I was kind of confused. This is an MSVC optimization limitation, then.

Adbot
ADBOT LOVES YOU

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed

rjmccall posted:

This code is actually comparing static types, though, so it should be equivalent to std::is_same. If the code were using typeid(butt) then you'd be right.

Oh, yeah, then it's just kinda dumb to use typeid.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply