CBE: fix windows test failures

This commit is contained in:
Jacob Young 2023-02-21 15:05:41 -05:00
parent 434c6f42ca
commit 248fb40dcc
4 changed files with 129 additions and 102 deletions

131
lib/zig.h
View File

@ -316,7 +316,7 @@ zig_extern void *memset (void *, int, size_t);
/* ===================== 8/16/32/64-bit Integer Support ===================== */
#if __STDC_VERSION__ >= 199901L
#if __STDC_VERSION__ >= 199901L || _MSC_VER
#include <stdint.h>
#else
@ -1923,6 +1923,7 @@ typedef double zig_f16;
#define zig_make_f16(fp, repr) fp
#elif LDBL_MANT_DIG == 11
#define zig_bitSizeOf_c_longdouble 16
typedef uint16_t zig_repr_c_longdouble;
typedef long double zig_f16;
#define zig_make_f16(fp, repr) fp##l
#elif FLT16_MANT_DIG == 11 && (zig_has_builtin(inff16) || defined(zig_gnuc))
@ -1959,6 +1960,7 @@ typedef double zig_f32;
#define zig_make_f32(fp, repr) fp
#elif LDBL_MANT_DIG == 24
#define zig_bitSizeOf_c_longdouble 32
typedef uint32_t zig_repr_c_longdouble;
typedef long double zig_f32;
#define zig_make_f32(fp, repr) fp##l
#elif FLT32_MANT_DIG == 24
@ -1982,6 +1984,7 @@ typedef int32_t zig_f32;
#if _MSC_VER
#ifdef ZIG_TARGET_ABI_MSVC
#define zig_bitSizeOf_c_longdouble 64
typedef uint64_t zig_repr_c_longdouble;
#endif
#define zig_make_special_constant_f64(sign, name, arg, repr) sign zig_make_f64(zig_msvc_flt_##name, )
#else /* _MSC_VER */
@ -1995,6 +1998,7 @@ typedef double zig_f64;
#define zig_make_f64(fp, repr) fp
#elif LDBL_MANT_DIG == 53
#define zig_bitSizeOf_c_longdouble 64
typedef uint64_t zig_repr_c_longdouble;
typedef long double zig_f64;
#define zig_make_f64(fp, repr) fp##l
#elif FLT64_MANT_DIG == 53
@ -2027,6 +2031,7 @@ typedef double zig_f80;
#define zig_make_f80(fp, repr) fp
#elif LDBL_MANT_DIG == 64
#define zig_bitSizeOf_c_longdouble 80
typedef zig_u128 zig_repr_c_longdouble;
typedef long double zig_f80;
#define zig_make_f80(fp, repr) fp##l
#elif FLT80_MANT_DIG == 64
@ -2062,6 +2067,7 @@ typedef double zig_f128;
#define zig_make_f128(fp, repr) fp
#elif LDBL_MANT_DIG == 113
#define zig_bitSizeOf_c_longdouble 128
typedef zig_u128 zig_repr_c_longdouble;
typedef long double zig_f128;
#define zig_make_f128(fp, repr) fp##l
#elif FLT128_MANT_DIG == 113
@ -2099,9 +2105,10 @@ typedef zig_i128 zig_f128;
#ifdef zig_bitSizeOf_c_longdouble
#ifdef ZIG_TARGET_ABI_MSVC
typedef double zig_c_longdouble;
#undef zig_bitSizeOf_c_longdouble
#define zig_bitSizeOf_c_longdouble 64
typedef uint64_t zig_repr_c_longdouble;
typedef zig_f64 zig_c_longdouble;
#define zig_make_c_longdouble(fp, repr) fp
#else
typedef long double zig_c_longdouble;
@ -2113,6 +2120,7 @@ typedef long double zig_c_longdouble;
#undef zig_has_c_longdouble
#define zig_has_c_longdouble 0
#define zig_bitSizeOf_c_longdouble 80
typedef zig_u128 zig_repr_c_longdouble;
#define zig_compiler_rt_abbrev_c_longdouble zig_compiler_rt_abbrev_f80
#define zig_bitSizeOf_repr_c_longdouble 128
typedef zig_i128 zig_c_longdouble;
@ -2126,21 +2134,18 @@ typedef zig_i128 zig_c_longdouble;
#if !zig_has_float_builtins
#define zig_float_from_repr(Type, ReprType) \
static inline zig_##Type zig_float_from_repr_##Type(zig_##ReprType repr) { \
return *((zig_##Type*)&repr); \
static inline zig_##Type zig_float_from_repr_##Type(ReprType repr) { \
zig_##Type result; \
memcpy(&result, &repr, sizeof(result)); \
return result; \
}
zig_float_from_repr(f16, u16)
zig_float_from_repr(f32, u32)
zig_float_from_repr(f64, u64)
zig_float_from_repr(f80, u128)
zig_float_from_repr(f128, u128)
#if zig_bitSizeOf_c_longdouble == 80
zig_float_from_repr(c_longdouble, u128)
#else
#define zig_expand_float_from_repr(Type, ReprType) zig_float_from_repr(Type, ReprType)
zig_expand_float_from_repr(c_longdouble, zig_expand_concat(u, zig_bitSizeOf_c_longdouble))
#endif
zig_float_from_repr(f16, uint16_t)
zig_float_from_repr(f32, uint32_t)
zig_float_from_repr(f64, uint64_t)
zig_float_from_repr(f80, zig_u128)
zig_float_from_repr(f128, zig_u128)
zig_float_from_repr(c_longdouble, zig_repr_c_longdouble)
#endif
#define zig_cast_f16 (zig_f16)
@ -2288,98 +2293,98 @@ zig_float_builtins(c_longdouble)
// TODO: zig_msvc_atomic_load should load 32 bit without interlocked on x86, and load 64 bit without interlocked on x64
#define zig_msvc_atomics(Type, suffix) \
static inline bool zig_msvc_cmpxchg_##Type(zig_##Type volatile* obj, zig_##Type* expected, zig_##Type desired) { \
zig_##Type comparand = *expected; \
zig_##Type initial = _InterlockedCompareExchange##suffix(obj, desired, comparand); \
#define zig_msvc_atomics(ZigType, Type, suffix) \
static inline bool zig_msvc_cmpxchg_##ZigType(Type volatile* obj, Type* expected, Type desired) { \
Type comparand = *expected; \
Type initial = _InterlockedCompareExchange##suffix(obj, desired, comparand); \
bool exchanged = initial == comparand; \
if (!exchanged) { \
*expected = initial; \
} \
return exchanged; \
} \
static inline zig_##Type zig_msvc_atomicrmw_xchg_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_xchg_##ZigType(Type volatile* obj, Type value) { \
return _InterlockedExchange##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomicrmw_add_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_add_##ZigType(Type volatile* obj, Type value) { \
return _InterlockedExchangeAdd##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomicrmw_sub_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_sub_##ZigType(Type volatile* obj, Type value) { \
bool success = false; \
zig_##Type new; \
zig_##Type prev; \
Type new; \
Type prev; \
while (!success) { \
prev = *obj; \
new = prev - value; \
success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \
success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \
} \
return prev; \
} \
static inline zig_##Type zig_msvc_atomicrmw_or_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_or_##ZigType(Type volatile* obj, Type value) { \
return _InterlockedOr##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomicrmw_xor_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_xor_##ZigType(Type volatile* obj, Type value) { \
return _InterlockedXor##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomicrmw_and_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_and_##ZigType(Type volatile* obj, Type value) { \
return _InterlockedAnd##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomicrmw_nand_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_nand_##ZigType(Type volatile* obj, Type value) { \
bool success = false; \
zig_##Type new; \
zig_##Type prev; \
Type new; \
Type prev; \
while (!success) { \
prev = *obj; \
new = ~(prev & value); \
success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \
success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \
} \
return prev; \
} \
static inline zig_##Type zig_msvc_atomicrmw_min_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_min_##ZigType(Type volatile* obj, Type value) { \
bool success = false; \
zig_##Type new; \
zig_##Type prev; \
Type new; \
Type prev; \
while (!success) { \
prev = *obj; \
new = value < prev ? value : prev; \
success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \
success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \
} \
return prev; \
} \
static inline zig_##Type zig_msvc_atomicrmw_max_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline Type zig_msvc_atomicrmw_max_##ZigType(Type volatile* obj, Type value) { \
bool success = false; \
zig_##Type new; \
zig_##Type prev; \
Type new; \
Type prev; \
while (!success) { \
prev = *obj; \
new = value > prev ? value : prev; \
success = zig_msvc_cmpxchg_##Type(obj, &prev, new); \
success = zig_msvc_cmpxchg_##ZigType(obj, &prev, new); \
} \
return prev; \
} \
static inline void zig_msvc_atomic_store_##Type(zig_##Type volatile* obj, zig_##Type value) { \
static inline void zig_msvc_atomic_store_##ZigType(Type volatile* obj, Type value) { \
_InterlockedExchange##suffix(obj, value); \
} \
static inline zig_##Type zig_msvc_atomic_load_##Type(zig_##Type volatile* obj) { \
static inline Type zig_msvc_atomic_load_##ZigType(Type volatile* obj) { \
return _InterlockedOr##suffix(obj, 0); \
}
zig_msvc_atomics(u8, 8)
zig_msvc_atomics(i8, 8)
zig_msvc_atomics(u16, 16)
zig_msvc_atomics(i16, 16)
zig_msvc_atomics(u32, )
zig_msvc_atomics(i32, )
zig_msvc_atomics( u8, uint8_t, 8)
zig_msvc_atomics( i8, int8_t, 8)
zig_msvc_atomics(u16, uint16_t, 16)
zig_msvc_atomics(i16, int16_t, 16)
zig_msvc_atomics(u32, uint32_t, )
zig_msvc_atomics(i32, int32_t, )
#if _M_X64
zig_msvc_atomics(u64, 64)
zig_msvc_atomics(i64, 64)
zig_msvc_atomics(u64, uint64_t, 64)
zig_msvc_atomics(i64, int64_t, 64)
#endif
#define zig_msvc_flt_atomics(Type, ReprType, suffix) \
static inline bool zig_msvc_cmpxchg_##Type(zig_##Type volatile* obj, zig_##Type* expected, zig_##Type desired) { \
zig_##ReprType comparand = *((zig_##ReprType*)expected); \
zig_##ReprType initial = _InterlockedCompareExchange##suffix((zig_##ReprType volatile*)obj, *((zig_##ReprType*)&desired), comparand); \
ReprType comparand = *((ReprType*)expected); \
ReprType initial = _InterlockedCompareExchange##suffix((ReprType volatile*)obj, *((ReprType*)&desired), comparand); \
bool exchanged = initial == comparand; \
if (!exchanged) { \
*expected = *((zig_##Type*)&initial); \
@ -2387,35 +2392,35 @@ zig_msvc_atomics(i64, 64)
return exchanged; \
} \
static inline zig_##Type zig_msvc_atomicrmw_xchg_##Type(zig_##Type volatile* obj, zig_##Type value) { \
zig_##ReprType initial = _InterlockedExchange##suffix((zig_##ReprType volatile*)obj, *((zig_##ReprType*)&value)); \
ReprType initial = _InterlockedExchange##suffix((ReprType volatile*)obj, *((ReprType*)&value)); \
return *((zig_##Type*)&initial); \
} \
static inline zig_##Type zig_msvc_atomicrmw_add_##Type(zig_##Type volatile* obj, zig_##Type value) { \
bool success = false; \
zig_##ReprType new; \
ReprType new; \
zig_##Type prev; \
while (!success) { \
prev = *obj; \
new = prev + value; \
success = zig_msvc_cmpxchg_##Type(obj, &prev, *((zig_##ReprType*)&new)); \
success = zig_msvc_cmpxchg_##Type(obj, &prev, *((ReprType*)&new)); \
} \
return prev; \
} \
static inline zig_##Type zig_msvc_atomicrmw_sub_##Type(zig_##Type volatile* obj, zig_##Type value) { \
bool success = false; \
zig_##ReprType new; \
ReprType new; \
zig_##Type prev; \
while (!success) { \
prev = *obj; \
new = prev - value; \
success = zig_msvc_cmpxchg_##Type(obj, &prev, *((zig_##ReprType*)&new)); \
success = zig_msvc_cmpxchg_##Type(obj, &prev, *((ReprType*)&new)); \
} \
return prev; \
}
zig_msvc_flt_atomics(f32, u32, )
zig_msvc_flt_atomics(f32, uint32_t, )
#if _M_X64
zig_msvc_flt_atomics(f64, u64, 64)
zig_msvc_flt_atomics(f64, uint64_t, 64)
#endif
#if _M_IX86
@ -2426,11 +2431,11 @@ static inline void zig_msvc_atomic_barrier() {
}
}
static inline void* zig_msvc_atomicrmw_xchg_p32(void** obj, uint32_t* arg) {
static inline void* zig_msvc_atomicrmw_xchg_p32(void** obj, void* arg) {
return _InterlockedExchangePointer(obj, arg);
}
static inline void zig_msvc_atomic_store_p32(void** obj, uint32_t* arg) {
static inline void zig_msvc_atomic_store_p32(void** obj, void* arg) {
_InterlockedExchangePointer(obj, arg);
}
@ -2448,11 +2453,11 @@ static inline bool zig_msvc_cmpxchg_p32(void** obj, void** expected, void* desir
return exchanged;
}
#else /* _M_IX86 */
static inline void* zig_msvc_atomicrmw_xchg_p64(void** obj, uint64_t* arg) {
static inline void* zig_msvc_atomicrmw_xchg_p64(void** obj, void* arg) {
return _InterlockedExchangePointer(obj, arg);
}
static inline void zig_msvc_atomic_store_p64(void** obj, uint64_t* arg) {
static inline void zig_msvc_atomic_store_p64(void** obj, void* arg) {
_InterlockedExchangePointer(obj, arg);
}

View File

@ -2058,6 +2058,7 @@ fn renderTypePrefix(
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> |tag| try w.writeAll(@tagName(tag)),
.pointer,
@ -2225,6 +2226,7 @@ fn renderTypeSuffix(
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> {},
.pointer,
@ -3062,6 +3064,7 @@ fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
return CValue.none;
}
const inst_ty = f.air.typeOfIndex(inst);
const ptr_ty = f.air.typeOf(bin_op.lhs);
const child_ty = ptr_ty.childType();
@ -3076,7 +3079,9 @@ fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
const writer = f.object.writer();
const local = try f.allocLocal(inst, f.air.typeOfIndex(inst));
try f.writeCValue(writer, local, .Other);
try writer.writeAll(" = &(");
try writer.writeAll(" = (");
try f.renderTypecast(writer, inst_ty);
try writer.writeAll(")&(");
if (ptr_ty.ptrSize() == .One) {
// It's a pointer to an array, so we need to de-reference.
try f.writeCValueDeref(writer, ptr);
@ -3902,32 +3907,31 @@ fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: u8) !CValue {
try reap(f, inst, &.{ bin_op.lhs, bin_op.rhs });
const inst_ty = f.air.typeOfIndex(inst);
const elem_ty = switch (inst_ty.ptrSize()) {
.One => blk: {
const array_ty = inst_ty.childType();
break :blk array_ty.childType();
},
else => inst_ty.childType(),
};
const elem_ty = inst_ty.elemType2();
// We must convert to and from integer types to prevent UB if the operation
// results in a NULL pointer, or if LHS is NULL. The operation is only UB
// if the result is NULL and then dereferenced.
const local = try f.allocLocal(inst, inst_ty);
const writer = f.object.writer();
try f.writeCValue(writer, local, .Other);
try writer.writeAll(" = (");
try f.renderTypecast(writer, inst_ty);
try writer.writeAll(")(((uintptr_t)");
try f.writeCValue(writer, lhs, .Other);
try writer.writeAll(") ");
try writer.writeByte(operator);
try writer.writeAll(" (");
try f.writeCValue(writer, rhs, .Other);
try writer.writeAll("*sizeof(");
try f.renderTypecast(writer, elem_ty);
try writer.writeAll(")));\n");
try writer.writeAll(" = ");
if (elem_ty.hasRuntimeBitsIgnoreComptime()) {
// We must convert to and from integer types to prevent UB if the operation
// results in a NULL pointer, or if LHS is NULL. The operation is only UB
// if the result is NULL and then dereferenced.
try writer.writeByte('(');
try f.renderTypecast(writer, inst_ty);
try writer.writeAll(")(((uintptr_t)");
try f.writeCValue(writer, lhs, .Other);
try writer.writeAll(") ");
try writer.writeByte(operator);
try writer.writeAll(" (");
try f.writeCValue(writer, rhs, .Other);
try writer.writeAll("*sizeof(");
try f.renderTypecast(writer, elem_ty);
try writer.writeAll(")))");
} else try f.writeCValue(writer, lhs, .Initializer);
try writer.writeAll(";\n");
return local;
}
@ -5264,21 +5268,21 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
else => unreachable,
};
try writer.writeByte('&');
switch (field_loc) {
.begin, .end => {
try writer.writeByte('(');
try f.writeCValue(writer, struct_ptr, .Other);
try writer.print(")[{}]", .{
@boolToInt(field_loc == .end and struct_ty.hasRuntimeBitsIgnoreComptime()),
});
},
.field => |field| if (extra_name != .none) {
try f.writeCValueDerefMember(writer, struct_ptr, extra_name);
try writer.writeByte('.');
try f.writeCValue(writer, field, .Other);
} else try f.writeCValueDerefMember(writer, struct_ptr, field),
}
if (struct_ty.hasRuntimeBitsIgnoreComptime()) {
try writer.writeByte('&');
switch (field_loc) {
.begin, .end => {
try writer.writeByte('(');
try f.writeCValue(writer, struct_ptr, .Other);
try writer.print(")[{}]", .{@boolToInt(field_loc == .end)});
},
.field => |field| if (extra_name != .none) {
try f.writeCValueDerefMember(writer, struct_ptr, extra_name);
try writer.writeByte('.');
try f.writeCValue(writer, field, .Other);
} else try f.writeCValueDerefMember(writer, struct_ptr, field),
}
} else try f.writeCValue(writer, struct_ptr, .Other);
try writer.writeAll(";\n");
return local;
}

View File

@ -102,6 +102,7 @@ pub const CType = extern union {
zig_f64,
zig_f80,
zig_f128,
zig_c_longdouble, // Keep last_no_payload_tag updated!
// After this, the tag requires a payload.
pointer,
@ -127,7 +128,7 @@ pub const CType = extern union {
function,
varargs_function,
pub const last_no_payload_tag = Tag.zig_f128;
pub const last_no_payload_tag = Tag.zig_c_longdouble;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
pub fn hasPayload(self: Tag) bool {
@ -177,6 +178,7 @@ pub const CType = extern union {
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> @compileError("Type Tag " ++ @tagName(self) ++ " has no payload"),
.pointer,
@ -557,6 +559,7 @@ pub const CType = extern union {
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> false,
.pointer,
@ -674,6 +677,7 @@ pub const CType = extern union {
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> {},
.pointer,
@ -980,7 +984,7 @@ pub const CType = extern union {
.f64 => .zig_f64,
.f80 => .zig_f80,
.f128 => .zig_f128,
.c_longdouble => .@"long double",
.c_longdouble => .zig_c_longdouble,
else => unreachable,
}),
@ -1374,6 +1378,7 @@ pub const CType = extern union {
.zig_f64,
.zig_f80,
.zig_f128,
.zig_c_longdouble,
=> return self,
.pointer,

View File

@ -7,6 +7,7 @@ const is_x86_64_linux = builtin.cpu.arch == .x86_64 and builtin.os.tag == .linux
comptime {
if (builtin.zig_backend != .stage2_arm and
builtin.zig_backend != .stage2_aarch64 and
!(builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) and // MSVC doesn't support inline assembly
is_x86_64_linux)
{
asm (
@ -24,6 +25,8 @@ test "module level assembly" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
if (is_x86_64_linux) {
try expect(this_is_my_alias() == 1234);
}
@ -36,6 +39,8 @@ test "output constraint modifiers" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
// This is only testing compilation.
var a: u32 = 3;
asm volatile (""
@ -57,6 +62,8 @@ test "alternative constraints" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
// Make sure we allow commas as a separator for alternative constraints.
var a: u32 = 3;
asm volatile (""
@ -73,6 +80,8 @@ test "sized integer/float in asm input" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
asm volatile (""
:
: [_] "m" (@as(usize, 3)),
@ -122,6 +131,8 @@ test "struct/array/union types as input values" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
asm volatile (""
:
: [_] "m" (@as([1]u32, undefined)),
@ -146,6 +157,8 @@ test "asm modifiers (AArch64)" {
if (builtin.target.cpu.arch != .aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c and builtin.os.tag == .windows) return error.SkipZigTest; // MSVC doesn't support inline assembly
var x: u32 = 15;
const double = asm ("add %[ret:w], %[in:w], %[in:w]"
: [ret] "=r" (-> u32),