Zig MSCV ABI/C-Translate Struggles

I am having issues where Zig is translating C code into “@compileError(“unable to resolve function type clang.TypeClass.MacroQualified”)” problem is, on other systems it translates fine (Linux/MacOS). A set of core functions are like this and would require me wrap just about every function into an object file which defeats the purpose of wanting to use Zig in this context.

Right now this is what I use for the zig build-lib:

..\zig-windows-x86_64-0.13.0\zig.exe build-lib ext.zig
  -fno-omit-frame-pointer
  -freference-trace
  -funwind-tables
  -fallow-shlib-undefined
  -fno-strip
  -dynamic
  -target x86_64-windows-msvc
  -I./build/php-8.4-non-zts-debug/include
  -I./build/php-8.4-non-zts-debug/include/main
  -I./build/php-8.4-non-zts-debug/include/Zend
  -I./build/php-8.4-non-zts-debug/include/TSRM
  -I./build/php-8.4-non-zts-debug/include/ext
  -Dtarget=native
  -D_DISABLE_DEPRECATED_WARNINGS
  -D_WIN32=1
  -DZEND_WIN32=1
  -DPHP_WIN32=1
  -D_USRDLL
  -DPHP8DLLTS_EXPORTS
  -DPHP_EXPORTS
  -DLIBZEND_EXPORTS
  -DTSRM_EXPORTS
  -DSAPI_EXPORTS
  -DCOMPILE_DL_EXT
  -D_WINDOWS
  -D_MBCS
  -D_USE_MATH_DEFINES
  -DPHP_SIMD_SCALE=SSE2
  -DFD_SETSIZE=256
  -DNDEBUG
  -DNDebug
  -DCOMPILE_DL_EXT2
  -DEXT2_EXPORTS=1
  -D__zig__
  -D_M_X64
  -D_AMD64_
  -D_WIN32_WINNT=0x0A00
  -D__STDC_LIMIT_MACROS
  -D_CRT_SECURE_NO_WARNINGS
  -DO_CREAT=0x0100
  -isystem C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.42.34433\\include
  -isystem C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\ucrt
  -isystem C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\shared
  -isystem C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\um
  -L C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.42.34433\\lib\\x64
  -L C:\\Program Files (x86)\\Windows Kits\\10\\Lib\\10.0.22000.0\\ucrt\\x64
  -L C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\lib\
  -L C:\\projects\\zig-php-ext\\build\\php-8.4-non-zts-debug\\lib
  -lc
  --verbose-link
  -lkernel32
  -lole32
  -luser32
  -ladvapi32
  -lshell32
  -lws2_32
  -lDnsapi
  -lpsapi
  -lbcrypt
  -lphp8
  -lucrt
  -O Debug
  -fPIC
  -I.
  -cflags
  -fms-extensions
  -mdefault-callconv=cdecl
  -- wrapper.o

The result in my cimport.zig is the following on Windows:

pub const _emalloc = @compileError("unable to resolve function type clang.TypeClass.MacroQualified");
// ./build/php-8.4-non-zts-debug/include/Zend/zend_alloc.h:67:53
pub extern fn _safe_emalloc(nmemb: usize, size: usize, offset: usize) ?*anyopaque;
pub extern fn _safe_malloc(nmemb: usize, size: usize, offset: usize) ?*anyopaque;
pub extern fn _efree(ptr: ?*anyopaque) void;
pub const _ecalloc = @compileError("unable to resolve function type clang.TypeClass.MacroQualified");
// ./build/php-8.4-non-zts-debug/include/Zend/zend_alloc.h:71:53
pub const _erealloc = @compileError("unable to resolve function type clang.TypeClass.MacroQualified");
// ./build/php-8.4-non-zts-debug/include/Zend/zend_alloc.h:72:31
pub const _erealloc2 = @compileError("unable to resolve function type clang.TypeClass.MacroQualified");
// ./build/php-8.4-non-zts-debug/include/Zend/zend_alloc.h:73:31

This _emalloc is a core issue not being defined. On macOS I get:

pub extern fn _emalloc(size: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;
pub extern fn _safe_emalloc(nmemb: usize, size: usize, offset: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;
pub extern fn _safe_malloc(nmemb: usize, size: usize, offset: usize) ?*anyopaque;
pub extern fn _efree(ptr: ?*anyopaque, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) void;
pub extern fn _ecalloc(nmemb: usize, size: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;
pub extern fn _erealloc(ptr: ?*anyopaque, size: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;
pub extern fn _erealloc2(ptr: ?*anyopaque, size: usize, copy_size: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;

The general issue is that the source code I rely on seems to use MSVC with C++ compiler flags because max_align_t is missing from MSVC’s C compiler version. However Zig provides max_align_t via lib\include\__stddef_max_align_t.h.

Here is a snippet of the file that defines _emalloc:


#ifndef ZEND_ALLOC_H
#define ZEND_ALLOC_H

#include <stdio.h>

#include "zend_types.h"
#include "../TSRM/TSRM.h"

#ifndef ZEND_MM_ALIGNMENT
# error "ZEND_MM_ALIGNMENT was not defined during configure"
#endif

#define ZEND_MM_ALIGNMENT_MASK ~(ZEND_MM_ALIGNMENT - 1)

#define ZEND_MM_ALIGNED_SIZE(size)	(((size) + ZEND_MM_ALIGNMENT - 1) & ZEND_MM_ALIGNMENT_MASK)

#define ZEND_MM_ALIGNED_SIZE_EX(size, alignment) \
	(((size) + ((alignment) - 1)) & ~((alignment) - 1))

typedef struct _zend_leak_info {
	void *addr;
	size_t size;
	const char *filename;
	const char *orig_filename;
	uint32_t lineno;
	uint32_t orig_lineno;
} zend_leak_info;

#if ZEND_DEBUG
typedef struct _zend_mm_debug_info {
	size_t             size;
	const char        *filename;
	const char        *orig_filename;
	uint32_t               lineno;
	uint32_t               orig_lineno;
} zend_mm_debug_info;

# define ZEND_MM_OVERHEAD ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info))
#else
# define ZEND_MM_OVERHEAD 0
#endif

BEGIN_EXTERN_C()

ZEND_API ZEND_ATTRIBUTE_MALLOC char*  ZEND_FASTCALL zend_strndup(const char *s, size_t length);

ZEND_API ZEND_ATTRIBUTE_MALLOC void*  ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(1);
ZEND_API ZEND_ATTRIBUTE_MALLOC void*  ZEND_FASTCALL _safe_emalloc(size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API ZEND_ATTRIBUTE_MALLOC void*  ZEND_FASTCALL _safe_malloc(size_t nmemb, size_t size, size_t offset);
ZEND_API void   ZEND_FASTCALL _efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API ZEND_ATTRIBUTE_MALLOC void*  ZEND_FASTCALL _ecalloc(size_t nmemb, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE2(1,2);
ZEND_API void*  ZEND_FASTCALL _erealloc(void *ptr, size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(2);
ZEND_API void*  ZEND_FASTCALL _erealloc2(void *ptr, size_t size, size_t copy_size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(2);
ZEND_API void*  ZEND_FASTCALL _safe_erealloc(void *ptr, size_t nmemb, size_t size, size_t offset ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API void*  ZEND_FASTCALL _safe_realloc(void *ptr, size_t nmemb, size_t size, size_t offset);
ZEND_API ZEND_ATTRIBUTE_MALLOC char*  ZEND_FASTCALL _estrdup(const char *s ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API ZEND_ATTRIBUTE_MALLOC char*  ZEND_FASTCALL _estrndup(const char *s, size_t length ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);
ZEND_API size_t ZEND_FASTCALL _zend_mem_block_size(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);

#include "zend_alloc_sizes.h"

/* _emalloc() & _efree() specialization */
#if !ZEND_DEBUG && defined(HAVE_BUILTIN_CONSTANT_P)

# define _ZEND_BIN_ALLOCATOR_DEF(_num, _size, _elements, _pages, x, y) \
	ZEND_API ZEND_ATTRIBUTE_MALLOC void* ZEND_FASTCALL _emalloc_  ## _size(void);

ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_DEF, x, y)

ZEND_API ZEND_ATTRIBUTE_MALLOC void* ZEND_FASTCALL _emalloc_large(size_t size) ZEND_ATTRIBUTE_ALLOC_SIZE(1);
ZEND_API ZEND_ATTRIBUTE_MALLOC void* ZEND_FASTCALL _emalloc_huge(size_t size) ZEND_ATTRIBUTE_ALLOC_SIZE(1);

# define _ZEND_BIN_ALLOCATOR_SELECTOR_START(_num, _size, _elements, _pages, size, y) \
	((size <= _size) ? _emalloc_ ## _size() :
# define _ZEND_BIN_ALLOCATOR_SELECTOR_END(_num, _size, _elements, _pages, size, y) \
	)

# define ZEND_ALLOCATOR(size) \
	ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_START, size, y) \
	((size <= ZEND_MM_MAX_LARGE_SIZE) ? _emalloc_large(size) : _emalloc_huge(size)) \
	ZEND_MM_BINS_INFO(_ZEND_BIN_ALLOCATOR_SELECTOR_END, size, y)

# define _emalloc(size) \
	(__builtin_constant_p(size) ? \
		ZEND_ALLOCATOR(size) \
	: \
		_emalloc(size) \
	)

There are two implementations one if there is debug turned on, another if not.

ZEND_API ZEND_ATTRIBUTE_MALLOC void*  ZEND_FASTCALL _emalloc(size_t size ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC) ZEND_ATTRIBUTE_ALLOC_SIZE(1);
# define _emalloc(size) \
	(__builtin_constant_p(size) ? \
		ZEND_ALLOCATOR(size) \
	: \
		_emalloc(size) \
	)

Zig can seem to work and provide a correct version, IF I can expand the C macros first:

Debug Version

zig clang -E 
    -DPHP_WIN32 
    -D_M_X64 
    -D_AMD64_ 
    -D_WIN32_WINNT=0x0A00 
    -D__STDC_LIMIT_MACROS 
    -D_CRT_SECURE_NO_WARNINGS 
    -DZEND_WIN32 
    -DZEND_DEBUG 
    -D__zig__ 
    -DO_CREAT=0x0100 
    -DARCH_X86_64=1 
    -I"C:\projects\zig-windows-x86_64-0.13.0\lib\include" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\main" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\Zend" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\TSRM" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\win32" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\ext" 
    -I"C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\sapi" 
    -isystem "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC\\14.42.34433\\include" 
    -isystem "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\ucrt" 
    -isystem "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\shared" 
    -isystem "C:\\Program Files (x86)\\Windows Kits\\10\\Include\\10.0.22000.0\\um" 
    C:\projects\zig-php-ext\build\php-8.4-non-zts-debug\include\Zend\zend_alloc.h > out.c

Resulting _emalloc

__attribute__((dllimport)) __attribute__ ((__malloc__)) void* _emalloc(size_t size , const char *__zend_filename, const uint32_t __zend_lineno , const char *__zend_orig_filename, const uint32_t __zend_orig_lineno) __attribute__ ((alloc_size(1)));

Then toss that into Zig to translate.

zig translate-c 
    -target x86_64-windows-msvc 
    -fno-omit-frame-pointer 
    -freference-trace 
    -fallow-shlib-undefined 
    -DPHP_WIN32 
    -DZEND_DEBUG 
    -D_M_X64 
    -D_AMD64_ 
    -DARCH_X86_64=1 
    -DWIN32_LEAN_AND_MEAN=1 
    -D_WIN32_WINNT=0x0A00 
    -D__STDC_LIMIT_MACROS 
    -D_CRT_SECURE_NO_WARNINGS 
    -DZEND_WIN32 
    -D__zig__ 
    -Doff_t=__int64 
    -DO_CREAT=0x0100 
    -Dsocklen_t=int 
    -cflags 
    -fms-extensions 
    -- out.c > out.zig

Resulting Zig output is then correct:

pub extern fn _emalloc(size: usize, __zend_filename: [*c]const u8, __zend_lineno: u32, __zend_orig_filename: [*c]const u8, __zend_orig_lineno: u32) ?*anyopaque;

Its not just this single function it happens in so many other places and just confused on a possible fix. Linux and macOS work fine like I said, but I’ve been crazy trying to translate MSVC flags with Clang, through Zig and this is the last step to figure out as everything else is working. I am able to compile a .dll, load into php.exe, run functions without memory allocation required by the PHP runtime, but as soon as I do it crashes due to incorrect translations.

I would currently avoid complex header files with translate-c, the tool seems to be written for simple header files (at the moment) and more complex logic only seems to be supported based on what was needed by individuals that may have added that support to it.

For example there are a lot of open issues around translate-c.

I think the reason for that is that the vast majority of users can get away with simple header files and thus never experience the problems with more complex header files being mistranslated or not translated.

I think because the basic usage is sufficient for a lot of users, fixing the more complex cases doesn’t have super high priority at the moment.

Using a different tool to make your header files simpler, like you have shown, may be the best option at the moment.

I think your only other options would be to either wait (potentially for quite a while), or participate in Zig’s development and try to improve translate-c yourself.

For the latter a good first step would be to figure out whether your case is already described by one of those open issues, or if it is something that was still unknown.

1 Like

I am not expecting C-Translate to handle this. The PHP C source code uses the C++ compiler flags on Windows with MSVC due to missing things. There is experimental clang support, but its far from what I’d want someone to try to be able to use Zig. MSVC has had poor C support so generally projects will just swap to the C++ compiler to compile their C code. I saw that Zig works around this but includes its own headers for fixes but these don’t seem to propagate correctly or something else is missing. I was hoping there was just some flag I as missing to get this to work. Anyways I’d been deal with this for weeks and yeah the only option I see working is to preprocess first to remove all the macros. I already have to patch the C code in the first place for example:

static inline size_t zend_call_stack_default_size(void)
{
#ifdef __linux__
	return 8 * 1024 * 1024;
#endif
#if defined(__FreeBSD__) || defined(__NetBSD__)
	return 4 * 1024 * 1024;
#endif
#if defined(__DragonFly__)
	return 2 * 1024 * 1024;
#endif
#ifdef __OpenBSD__
	return 512 * 1024;
#endif
#ifdef __APPLE__
	// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
	if (pthread_main_np()) {
		return 8 * 1024 * 1024;
	}
	return 512 * 1024;
#endif
#ifdef __HAIKU__
	return 64 * 4096;
#endif
#ifdef __sun
	return 8 * 4096;
#endif

	return 2 * 1024 * 1024;
}

Zig translates to a double return which Zig will not compile when the function is used. Which on linux translated to this:

pub fn zend_call_stack_default_size() callconv(.C) usize {
    return @as(usize, @bitCast(@as(c_long, (@as(c_int, 8) * @as(c_int, 1024)) * @as(c_int, 1024))));
    return @as(usize, @bitCast(@as(c_long, (@as(c_int, 2) * @as(c_int, 1024)) * @as(c_int, 1024))));
}

So I have to change out the code:

static inline size_t zend_call_stack_default_size(void)
{
#ifdef __linux__
    return 8 * 1024 * 1024;
#elif defined(__FreeBSD__) || defined(__NetBSD__)
    return 4 * 1024 * 1024;
#elif defined(__DragonFly__)
    return 2 * 1024 * 1024;
#elif defined(__OpenBSD__)
    return 512 * 1024;
#elif defined(__APPLE__)
    if (pthread_main_np()) {
        return 8 * 1024 * 1024;
    } else {
        return 512 * 1024;
    }
#elif defined(__HAIKU__)
    return 64 * 4096;
#elif defined(__sun)
    return 8 * 4096;
#else
    return 2 * 1024 * 1024;
#endif
}
1 Like

Can you clarify what you want help with?
It seems you already found some good solutions.

Do you just want to share what you have done and struggled with, hoping that someone has had similar issues and possibly found other tricks or other easier solutions?