Open Quuxplusone opened 7 years ago
Attached file_30761.txt
(6758 bytes, text/plain): C source, with disassembly and comparison to code generated by Visual Studio
I'm unable to reproduce this locally with clang 3.9 or trunk. My steps:
$ cat t.cpp
int g(int x) {
if (x == 42)
throw 1;
return x;
}
int f(int x) {
__try {
__try {
x = g(x);
} __except ((x == 42) ? 1 : 0) {
++x;
}
} __finally {
x *= 2;
}
return x;
}
int main() { return f(42); }
$ clang-cl -EHsc t.cpp && ./t.exe ; echo $?
86
$ clang-cl -O1 -EHsc t.cpp && ./t.exe ; echo $?
86
$ clang-cl -O2 -EHsc t.cpp && ./t.exe ; echo $?
86
This is using the MSVC 2015 x64 CRT. Can you elaborate on how your steps are
different? Thanks for the report.
I would guess that your case has a different result because it's C++, so will
be using the CxxFrameHandler3 personality, whereas mine is C so uses
__C_specific_handler. That changes everything to do with the LSDA (language-
specific data area) which is where my previous attachment indicated the problem
probably lies. Here's a smaller single-file example:
$ cat filters-small.c
#include <stdio.h>
#include <Windows.h>
extern void g(int x)
{
RaiseException(0xE0123456, 0, 0, NULL);
}
int f(int x)
{
__try {
__try {
g(x);
} __except((x == 42) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
++x;
}
} __finally {
x *= 2;
}
return x;
}
int main(void)
{
printf("Raising 42 -> 86: %d\n", f(42));
return 0;
}
$ clang -v
clang version 3.9.0 (branches/release_39)
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM-3.9.0\bin
$ clang filters-small.c
$ ./a.exe
Raising 42 -> 86: 170
$
I'm also building Clang from trunk sources to see if this is fixed there.
(In reply to comment #2)
> I would guess that your case has a different result because it's C++, so
> will be using the CxxFrameHandler3 personality, whereas mine is C so uses
> __C_specific_handler.
Using __try and __except always causes the affected function to use
__C_specific_handler, even in C++. I just didn't want to include windows.h so I
used C++ throw. :)
I'll try your example, thanks.
FWIW I get 86 at all optimizations levels, so I think this has been fixed.
Is that with 3.9 or trunk? I've been having problems building from trunk (possibly something to do with my Visual Studio installation), so I haven't been able to test against it.
Trunk, but 3.9 is very recent. Maybe this is a CRT difference?
BTW, trunk requires VS 2015 these days. That might be your issue.
Yes, I'm building with VS 2015. I think my build problems are all to do with the brain-damaged VM I'm running it on. We'll find out tomorrow.
Can you run with -O -S and post the results? I'm particularly interested in the LSDA, so I'd also like to see dumpbin /disasm and dumpbin /all of the object file (my interest in this stuff is driven by a need to generate LLIR for a compiler for a client's own programming language; the exception-handling is the hardest part).
Attached t.s
(5662 bytes, text/plain): O1 assembly
I still get 170 with a 4.0.0 trunk build (checked out yesterday). But further
experimentation suggests that it is something to do with the CRT, as if I run
in a VS2015 CMD after a vcvars64.bat then I get the right result. Linker,
perhaps?
I'll poke at this some more in the morning, to see if I can narrow it down.
What I'm interested in is ensuring I get the right object code (including LSDA)
from LLVM libraries linked into a client's compiler; if I have to tweak the
environment to do that then I need to know that. In the short term we're going
to be faking __C_specific_handler (by post-processing the UNWIND_INFO) and
rolling our own, so I need to dig through the entrails of this a bit more.
I have a long document reverse engineering C_specific_handler, which explains why the finally funclet is called. Possibly the C_specific_handler is different in VS2015's runtime? I can clean that document up and post it tomorrow.
The range check of the except label that you describe is hilariously naive. It assumes that the generated code remains in source order. LLVM's machine block placement puts the except code after the normal return, so it's definitely not in the try range of the try/finally. Given that exceptions are very unlikely, LLVM's code layout is probably better for performance.
I'm definitely curious to find out why this works with 2015, though. :)
Performance has to come second to semantic correctness, though. You can get the right semantics with the VS2010 runtime by adding additional ScopeRecords covering the handler code, which is what the VS2010 compiler will do if necessary. The interesting question is the semantics of the __C_specific_handler in VS2015. I'll look into that tomorrow, if I have time.
(In reply to comment #15)
> Performance has to come second to semantic correctness, though. You can get
> the right semantics with the VS2010 runtime by adding additional
> ScopeRecords covering the handler code, which is what the VS2010 compiler
> will do if necessary.
Sure, but the system is just doing what it was designed to do. We don't express
__C_specific_handler's invariant to block placement, so it lays code out this
way. Actually expressing that invariant would be hard, and I'm not sure it's
worth doing if what we already have works with more recent CRTs.
Maybe the clang executable could detect that it's going to link against an outdated version of __C_specific_handler? Basically if a developer has multiple versions of VS installed (not uncommon) and has run the wrong vcvars script most recently, then clang will silently produce executables which have this quite subtle wrong behaviour and are very difficult to diagnose.
Our project won't be affected: now that we understand the problem we can put checks in our build system.
file_30761.txt
(6758 bytes, text/plain)t.s
(5662 bytes, text/plain)