Handmade Hero»Forums»Code
51 posts
Using a pure C compiler instead of MSVC?
Greetings!

So far in HMH we've been using C++ and disabling features we don't need by the compiler switches. I tend to like the other way around, i.e. start with something basic and add the things I need instead of having to figure out what I don't need.

In my codebase I'm rarely using any C++ features at all. I'm getting by well enough even without function/operator overloading. So I figured why not just use a C compile instead? I remember Casey mentioning Tiny C (http://bellard.org/tcc/) as a decent and very fast compiler. (There's another crazy reason why I'm leaning more towards C, we hope to eventually port our game to PSX!)

I was wondering if any of you guys in the community have thought about this or tried this path before? is anyone using Tiny C or some other pure C compiler instead of MSVC? if so, what is your experience with it (does it spit out debug information/pdb files so I can debug in VS?) and what do you recommend.

Thanks!
vexe/elxenoanizd
Mārtiņš Možeiko
2559 posts / 2 projects
Using a pure C compiler instead of MSVC?
MSVC compiles C code without C++ features just fine. I recommend continue using MSVC. Rename file to have .c extension and no C++ will be allowed in C source.

MSVC doesn't support full C99 standard, but it supports enough of it, so you can use some very nice C99 features just fine (like designated initializers). Soon it will significantly improve with clang frontend.
51 posts
Using a pure C compiler instead of MSVC?
Thanks for the reply. I forgot to mention I do know about the switch that tells cl to compile my code as C I've tried it before. But I was wondering if anyone has used Tiny C before because those compile time benchmark numbers are looking really impressive.
Mārtiņš Možeiko
2559 posts / 2 projects
Using a pure C compiler instead of MSVC?
Yeah, but generated code compared to MSVC/clang/gcc C code won't be impressive :)
MSVC in C mode compiles much faster than in C++ mode.
51 posts
Using a pure C compiler instead of MSVC?
I guess we don't care about disabling RTTI and Exceptions if we switch MSVC to C mode, correct? What are some of the other switches that would not be necessary in C mode that we're currently using in HMH?
Mārtiņš Možeiko
2559 posts / 2 projects
Using a pure C compiler instead of MSVC?
Yes, for C code RTTI and regular exceptions doesn't mean anything. On Windows there is possibility to use SEH exceptions even in C code (with __try/__except keywords, not try/catch). They are part of OS and compiler generates additional information to support it. You can't really turn them off, but if you don't use them then you shouldn't see any extra penalty at runtime.

Other than that, there is not nothing else that you can turn off for C mode.
101 posts
Using a pure C compiler instead of MSVC?
Edited by pragmatic_hero on
mmozeiko
Yes, for C code RTTI and regular exceptions doesn't mean anything. On Windows there is possibility to use SEH exceptions even in C code (with __try/__except keywords, not try/catch). They are part of OS and compiler generates additional information to support it. You can't really turn them off, but if you don't use them then you shouldn't see any extra penalty at runtime.

Other than that, there is not nothing else that you can turn off for C mode.

Is there a way to do SEH - get __try/__except behavior - using another C compiler - TCC / GCC / Clang - on Windows?
TCC has an option to write inline assembly.

I'm relying on __try/__except when calling out to hotswappable DLLs.
But I'd like to have an option to replace MSVC C compiler with another one.

Mārtiņš Možeiko
2559 posts / 2 projects
Using a pure C compiler instead of MSVC?
Edited by Mārtiņš Možeiko on
Clang supports SEH on Windows exactly same as MSVC. This is for x86_64, no idea about SEH support with clang & 32-bit code.

GCC doesn't support it. But there is a small trick you can do - MinGW provides __try1 and __except1 macros which does almost same stuff as MSVC. It installs SEH handler with a bit of inline assembly. Though the handling syntax is a bit different, so you'll need to wrap this stuff in preprocessor ifdefs. Here's the definitions: https://github.com/Alexpux/mingw-...ingw-w64-headers/crt/excpt.h#L100 It's a bit tricky to use because it requires to write custom handler, but its possible.

Another option is just to use vectored exception handler to catch exceptions. It's a OS API, a bit uglier and slower (only when exception is thrown) - because it will get all exceptions (before SEH handlers) that are thrown, you need to filter only ones you are interested in. But will work with any compiler. You can install vectored exception handler over the area that possible can throw exceptions, it will get you callback in case exception happens, and later you can remove this handler. Here's example from MSDN: https://msdn.microsoft.com/en-us/library/ms681411.aspx
101 posts
Using a pure C compiler instead of MSVC?
Edited by pragmatic_hero on
I'm looking at that Vectored Exception Handler sample.
In cases where they don't use __try/__except, they step over the code which triggers the exception by adding +1 to instruction pointer:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
LONG WINAPI
VectoredHandlerSkip1(
    struct _EXCEPTION_POINTERS *ExceptionInfo
    )
{
    PCONTEXT Context;
    
    Sequence++;
    Context = ExceptionInfo->ContextRecord;
    Actual[0] = 0xcc;
#ifdef _AMD64_
    Context->Rip++;
#else
    Context->Eip++;
#endif    
    return EXCEPTION_CONTINUE_EXECUTION;
}


Which probably works for them - as the exception is raised by single null dereference.
1
2
3
4
5
void IllegalInst()
{
    char *ptr = 0;
    *ptr = 0;
}


Scenario for hotswapped DLL is more like this:
1
2
3
4
5
6
7
__try {
   function_pointer_to_dll_func(app_state); // function pointer is not null
}
__except {
    // 1. We want to know that an exception happened (at all)
    // 2. And this is where we want to end up to continue execution
}

Who knows what the value of instruction pointer has to be to end up in that except block (or just before __try block). And how to restore the state of the stack. Advancing IP one by one will just blow shit up to the point where it's unsalvageable.

There are two related functions RtlCaptureContext / RtlRestoreContext(this one is 64bit only), not sure if they apply here. (rtlcapturecontext before calling dll_func, restore context within the vectored exception handler?)
Mārtiņš Možeiko
2559 posts / 2 projects
Using a pure C compiler instead of MSVC?
Edited by Mārtiņš Možeiko on
You can play around setting/restoring CPU context: https://gist.github.com/mmozeiko/91a698249fdb9ba1c69dd1e0023552ce
Or if you don't want to use context API (to support 32-bit code), you can use setjmp/lonjmp from setjmp.h std header.

I didn't mention before, but using vectored exception handler there is no unwinding step. So no C++ destructors will never be called for local objects when exception is happening. If you need unwinding then you need to use SEH, or manually call unwinding which is more manual work.

Why do you need to handle exceptions from loading dll files? I mean the dll file can mess up process pretty badly (heap corruption, closing some OS handles, etc..) that there will be no way to resume execution even if you handle exception.
101 posts
Using a pure C compiler instead of MSVC?
Edited by pragmatic_hero on
Yes! Yes and Yes! Thank you.

I've been using __try/__catch extensively when calling out to hotswappable dll functions for quite a while now.
Time has shown that most exceptions, have been recoverable.
Basically whenever an exception happens within hotswappable function, it doesn't get called again until the new version of dll
(hopefully fixed) gets reloaded.

I also do some other "weird shit" - for example
In addition to passing a "game state" into the DLLs, DLLs can have - and do often have - static variables and state within them.
This state gets carefully copied over to the new dll.
Before this happens of course, introspection is run over the new sources (used to build the dll) to see what data is there and how can it be copied from old to new dll.

Having prototyped-on-structures separate from heap allocated "game-state" I believe contributes to shit blowing up more gracefully and in a recoverable manner.

As a result, I can prototype algorithms, gameplay stuff - that means adding new data structures and shuffling them around abit - without *ever* closing the running game. Eventually prototyped-stuff gets *solidified* and moved in "game state" proper.

This does require some thought and there are special functions for mutating the dll state module_init, module_destroy, module_before_reload, module_after_reload - (a part of reloadable code) - usually enough to preserve everything in the working order.

Shit does blow up quite fatally at times (usually MSVC debugger dies first), but if you get 2hrs of uninterrupted programming - for me that's a win. It's an addictively fun workflow to play with.

Now I'm trying to add TCC into the mix. 64bit TCC is buggy, so I'm stuck with trying to get 32bit TCC do __try/__catch.
Unfortunately, 32bit kernel32.dll doesn't have rtlRestoreContext.

TCC shows promise of being able to compile at 60fps on a ramdisk! Imagine all the possibilities (such as heap corruption at 60 FPS!)!

Edit: done, it all works now. Thank you, Martins!

101 posts
Using a pure C compiler instead of MSVC?
Edited by pragmatic_hero on
The end result looks roughly likes this (minus the printf's):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#define WIN32_EXCEPTION_LIST \
	FUN(EXCEPTION_ACCESS_VIOLATION) \
	FUN(EXCEPTION_ARRAY_BOUNDS_EXCEEDED) \
	FUN(EXCEPTION_BREAKPOINT) \
	FUN(EXCEPTION_DATATYPE_MISALIGNMENT) \
	FUN(EXCEPTION_FLT_DENORMAL_OPERAND) \
	FUN(EXCEPTION_FLT_DIVIDE_BY_ZERO) \
	FUN(EXCEPTION_FLT_INEXACT_RESULT) \
	FUN(EXCEPTION_FLT_INVALID_OPERATION) \
	FUN(EXCEPTION_FLT_OVERFLOW) \
	FUN(EXCEPTION_FLT_STACK_CHECK) \
	FUN(EXCEPTION_ILLEGAL_INSTRUCTION) \
	FUN(EXCEPTION_IN_PAGE_ERROR) \
	FUN(EXCEPTION_INT_DIVIDE_BY_ZERO) \
	FUN(EXCEPTION_INT_OVERFLOW) \
	FUN(EXCEPTION_INVALID_DISPOSITION) \
	FUN(EXCEPTION_NONCONTINUABLE_EXCEPTION) \
	FUN(EXCEPTION_PRIV_INSTRUCTION) \
	FUN(EXCEPTION_SINGLE_STEP) \
	FUN(EXCEPTION_STACK_OVERFLOW) 

#include <setjmp.h>
static jmp_buf exception_jumper;

static LONG WINAPI
VectoredHandler__(struct _EXCEPTION_POINTERS *ExceptionInfo)
{    	
	PCONTEXT Context;
	Context = ExceptionInfo->ContextRecord;	
	printf("\n---------------- EXCEPTION ---------------------\n");	
	DWORD except_code = ExceptionInfo->ExceptionRecord->ExceptionCode;
	#define FUN(E) if(except_code == E) {printf("Code 0x%x: %s \n", except_code, #E);} else
		WIN32_EXCEPTION_LIST {printf("UNKNOWN EXCEPTION CODE, wtf!\n");}
	#undef FUN
	printf("------------------------------------------------\n\n");	
	longjmp(exception_jumper, 1);
	return EXCEPTION_CONTINUE_EXECUTION;
}

#define TRY_EXCEPT(TRY, EXCEPT) \
	{	\
		PVOID vectored_exhandler; \
		vectored_exhandler = AddVectoredExceptionHandler(0,VectoredHandler__); \
		if(!setjmp(exception_jumper)) { \
			TRY \
		} else { \
			EXCEPT \
		} \
		RemoveVectoredExceptionHandler(vectored_exhandler);	\
	}

#define DLL_INVOKE(FUN_NAME, ...) if(FUN_NAME) { \
	TRY_EXCEPT( \
		{FUN_NAME(__VA_ARGS__);}, \
		{FUN_NAME = NULL; \
		printf("Culprit: `%s` REMOVED AND TERMINATED! \n\n", #FUN_NAME);} \
	); \
	} 


Then call module DLLs like this:
1
	DLL_INVOKE(module->mod_update, delta_time, data);