Removing the CRT when compiling with clang?

I've successfully removed the CRT when compiling with msvc but I'm having trouble finding the correct method to replace the c functions when compiling with clang. For example, for msvc you define the functions memset and memcpy (which the compiler uses internally I guess?) without the CRT like so:

#pragma function(memset)
void* memset(void* dest, int c, size_t count)
{
    char* bytes = (char*)dest;
    while(count--)
    {
        *bytes++ = (char)c;
    }

    return dest;
}

#pragma function(memcpy)
void* memcpy(void* dest, const void* src, size_t count)
{
    char* dest8 = (char*)dest;
    const char* src8 = (const char*)src;
    while(count--)
    {
        *dest8++ = *src8++;
    }
    return dest;
}

but I'm having trouble figuring out the equivalent method in clang/llvm (I'm currently getting an 'undefined symbol memset' error in clang). I've searched the docs but had trouble finding what I need. I've even asked on stack overflow with no satisfactory answers. So how do I do this with clang? Also, is there anything I need to do to remove the CRT when compiling with clang? Not sure how different it was from msvc in this regard.


Edited by Jason on

Is this for using clang to compile windows exe? Then this should work fine for clang.

Did you put this in .c file? Or if it is in .cpp file, then put extern "C" { ... } around it.


Edited by Mārtiņš Možeiko on

Ya, I'm using clang-cl currently to compile for windows and I'm compiling for c99. So the #pragma function() should work with clang? Here is my current minimal compilable example along with the build file I'm using:

win64_main.c

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdbool.h>

//This is normally a part of the c runtime library so have to define it here
int _fltused = 0x9875;

bool app_running = false;

LRESULT CALLBACK win32_program_window_callback(HWND WindowHandle, UINT Message, WPARAM wParam, LPARAM lParam) {
    LRESULT result = { 0 };

    //For hardware rendering
    HDC WindowContext = GetDC(WindowHandle);
    
    switch (Message)
    {
        case WM_CREATE: {
        }break;
        
        case WM_PAINT: {
            //To understand why you need a display buffer call here as well as game loop, see this post:
            //https://hero.handmade.network/forums/code-discussion/t/825-wm_paint_question_beginner
            PAINTSTRUCT Paint;
            HDC deviceContext = BeginPaint(WindowHandle, &Paint);

            EndPaint(WindowHandle, &Paint);
        } break;
        
        case WM_DESTROY: {
            app_running = false;
        } break;
        
        case WM_CLOSE: {
            app_running = false;
        } break;
        
        case WM_ACTIVATEAPP: {
        } break;
        
        default: {
            result = DefWindowProc(WindowHandle, Message, wParam, lParam);
        } break;
    }
    
    return result;
};

void win32_process_pending_messages(HWND window) {
    MSG message;
    while(PeekMessage(&message, 0, 0, 0, PM_REMOVE)) {
        switch(message.message) {
            case WM_QUIT: {
                app_running = false;
            }break;

            case WM_SYSKEYDOWN:
            case WM_SYSKEYUP:
            case WM_KEYDOWN:
            case WM_KEYUP: {

            }break;

            case WM_MOUSEWHEEL: {
                int delta = GET_WHEEL_DELTA_WPARAM(message.wParam);
            }break;

            case WM_LBUTTONDOWN:
            case WM_LBUTTONUP: {

            }break;

            default: {
                TranslateMessage(&message);
                DispatchMessageA(&message);
            }break;
        }
    }
}

void __stdcall WinMainCRTStartup() {
    SYSTEM_INFO sysInfo;
    GetSystemInfo(&sysInfo);
    
    WNDCLASS window_properties = {0};
    window_properties.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; //TODO: Check if OWNDC/HREDRAW/VEDRAW matter
    window_properties.lpfnWndProc = win32_program_window_callback;
    window_properties.hInstance = GetModuleHandle(0);
    window_properties.lpszClassName = "my_window_class";
    window_properties.hCursor = LoadCursor(NULL, IDC_ARROW);

    if(RegisterClass(&window_properties)) {
        DWORD window_styles = WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
        RECT rect = (RECT){ 0, 0, (LONG)1280, (LONG)720 };
        BOOL success = AdjustWindowRectEx(&rect, window_styles, false, 0);

        HWND window = CreateWindowEx(0, window_properties.lpszClassName, "My Window", WS_VISIBLE | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, 0, 0, window_properties.hInstance, 0);
        HDC window_context = GetDC(window);

        if(window && window_context) {
            app_running = true; 

            while(app_running) {
                win32_process_pending_messages(window);
            }

            PostMessage(window, WM_CLOSE, 0, 0);
        }
    }

    int result = 0;
    ExitProcess(result);
} 

clang build.bat file:

@echo off

REM This represents the full directory path to your batch file
set cwd=%~dp0

REM Equivalent to vcvarsall.bat
call "C:\msvc\setup.bat"

REM I believe above "call 'C:msvc\..." changes directorys so making sure to change back to current directory
cd /d %cwd%

REM -GS- tells compiler to not worry about security calls (like __security_cookie) that would typically be inserted with c libs. -Gs99999999999 basically says don't worry about __chkdsk for checking stack overruns
set compiler_flags=-Z7 -nologo -Oi -Od -Ob1 -WX -W4 -GR -EHa- -GS- -Gs999999999 -wd4505 -wd4101 -wd4530 -w14700 -wd4100 -we4820 -wd4201 -wd4189
REM -STACK:0x100000,0x100000 is setting function stack default to 1MB. Can increase this if we need to. Keep in mind though this also increases default stack size for all threads created
set linker_flags=-subsystem:windows -machine:x64 -incremental:no -nologo -opt:ref -debug -ignore:4099 -NODEFAULTLIB -STACK:0x100000,0x100000

set include_paths=-I"%cwd%source" -I"C:\msvc\Windows Kits\10\Include\10.0.22621.0\um"
set library_paths=-LIBPATH:"C:\msvc\Windows Kits\10\Lib\10.0.22621.0\um\x64"
set import_libraries="kernel32.lib" "user32.lib"

IF NOT EXIST bin mkdir bin
pushd bin

clang-cl /c ..\source\*.c %compiler_flags% %include_paths% -DBGZ_WIN64 -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -fno-builtin
lld-link *.obj -OUT:win64_main.exe %linker_flags% %library_paths% %import_libraries%

popd

With the #pragma function() clang is saying unknown pragma ignored which is what I expect since I think those are specific to the msvc compiler. If I remove the pragma stuff then I get an undefined memset symbol at line 99 (WNDCLASS window_properties = {0})


Edited by Jason on
Replying to mmozeiko (#26749)

Yes, removing #pragma function should be enough.

What is the exact error message you're getting? Are you sure you're recompiling all the relevant .obj files? Because you have *.obj for lld-link maybe it is picking up some old ones? You can add /verbose to lld-link to see what .obj files it is using.


Edited by Mārtiņš Možeiko on
Replying to boagz57 (#26750)

I've deleted all other files in my project and updated everything so the code I posted here is exactly what I'm tying to compile. I deleted the bin folder and still get this error:

clang_build.bat
lld-link: error: undefined symbol: memset
>>> referenced by D:\Software_Projects\bgz_engine\source\win64_main.c:85
>>>               win64_main.obj:(WinMainCRTStartup)

So in my head, this is happening because clang is inserting a memset call when I do WNDCLASS window_properties = {0}; but since I passed -fno-builtin then no default memset function is provided, so I need to provide it one correct?

Edit:

When passing the /verbose option this is the output:

lld-link: Reading win64_main.obj
lld-link: Directives: win64_main.obj:  /DEFAULTLIB:libcmt.lib /DEFAULTLIB:oldnames.lib /DEFAULTLIB:uuid.lib /DEFAULTLIB:uuid.lib
lld-link: Reading C:\msvc\Windows Kits\10\Lib\10.0.22621.0\um\x64\kernel32.lib
lld-link: Reading C:\msvc\Windows Kits\10\Lib\10.0.22621.0\um\x64\user32.lib
lld-link: Reading KERNEL32.dll
lld-link: Reading KERNEL32.dll
lld-link: Reading KERNEL32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Reading USER32.dll
lld-link: Entry name inferred: WinMainCRTStartup
lld-link: error: undefined symbol: memset
>>> referenced by D:\Software_Projects\bgz_engine\source\win64_main.c:85
>>>               win64_main.obj:(WinMainCRTStartup)

So I see /DEFAULTLIB:libcmt.lib /DEFAULTLIB:oldnames.lib which I'm not sure what oldnames.lib is and why libcmt.lib is still getting passed?


Replying to mmozeiko (#26751)

I've deleted all other files in my project and updated everything so the code I posted here is exactly what I'm tying to compile.

By "exactly what I'm trying to compile" you mean you updated the win32_main.c file to have memset function defined? Because you need to define it manually. I took your win32_main.c, and put memset code inside there and then it compiles & links for with with the same clang/lld arguments from your bat file.

but since I passed -fno-builtin then no default memset function is provided, so I need to provide it one correct?

-fno-builtin here is unrelated. It does not affect anything. What it is doing is opposite. In cases where you have simple function calls to memset, abs, alloca and similar compiler is allowed to replace them with non-function call if it wants. -fno-builtin prevents this, in cases where you want actual function calls.

So I see /DEFAULTLIB:libcmt.lib /DEFAULTLIB:oldnames.lib which I'm not sure what oldnames.lib is and why libcmt.lib is still getting passed?

That does not matter much, because you're passing -nodefaultlib Which makes linker to ignore any of /defaultlib arguments. Why they are there is because clang-cl.exe puts them on .obj file for linker to use some of default libs (which could be specified with #pragma comment ... too). If you want to avoid that, then pass extra -Zl argument to clang-cl.


Edited by Mārtiņš Možeiko on
Replying to boagz57 (#26752)

Oookay, I see. I was thinking I needed some extra stuff in order to actually define memset like I had to do in msvc with the #pragma function(memset) but I can just define it as normal. Thanks for the help and info.


Replying to mmozeiko (#26753)