zig/test/run_translated_c.zig

1930 lines
64 KiB
Zig
Raw Normal View History

const std = @import("std");
const tests = @import("tests.zig");
const nl = if (@import("builtin").os.tag == .windows) "\r\n" else "\n";
// *********************************************************
// * *
// * DO NOT ADD NEW CASES HERE *
// * instead add a file to test/cases/run_translated_c *
// * *
// *********************************************************
pub fn addCases(cases: *tests.RunTranslatedCContext) void {
cases.add("division of floating literals",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdio.h>
\\#define PI 3.14159265358979323846f
\\#define DEG2RAD (PI/180.0f)
\\int main(void) {
\\ printf("DEG2RAD is: %f\n", DEG2RAD);
\\ return 0;
\\}
, "DEG2RAD is: 0.017453" ++ nl);
cases.add("use global scope for record/enum/typedef type translation if needed",
\\void bar(void);
\\void baz(void);
\\struct foo { int x; };
\\void bar() {
\\ struct foo tmp;
\\}
\\
\\void baz() {
\\ struct foo tmp;
\\}
\\
\\int main(void) {
\\ bar();
\\ baz();
\\ return 0;
\\}
, "");
cases.add("failed macros are only declared once",
\\#define FOO =
\\#define FOO =
\\#define PtrToPtr64(p) ((void *POINTER_64) p)
\\#define STRUC_ALIGNED_STACK_COPY(t,s) ((CONST t *)(s))
\\#define bar = 0x
\\#define baz = 0b
\\int main(void) {}
, "");
cases.add("parenthesized string literal",
\\void foo(const char *s) {}
\\int main(void) {
\\ foo(("bar"));
\\}
, "");
cases.add("variable shadowing type type",
\\#include <stdlib.h>
\\int main() {
\\ int type = 1;
\\ if (type != 1) abort();
\\}
, "");
cases.add("assignment as expression",
\\#include <stdlib.h>
\\int main() {
\\ int a, b, c, d = 5;
\\ int e = a = b = c = d;
\\ if (e != 5) abort();
\\}
, "");
cases.add("static variable in block scope",
\\#include <stdlib.h>
\\int foo() {
\\ static int bar;
\\ bar += 1;
\\ return bar;
\\}
\\int main() {
\\ foo();
\\ foo();
\\ if (foo() != 3) abort();
\\}
, "");
cases.add("array initializer",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int a0[4] = {1};
\\ int a1[4] = {1,2,3,4};
\\ int s0 = 0, s1 = 0;
\\ for (int i = 0; i < 4; i++) {
\\ s0 += a0[i];
\\ s1 += a1[i];
\\ }
\\ if (s0 != 1) abort();
\\ if (s1 != 10) abort();
\\}
, "");
cases.add("forward declarations",
\\#include <stdlib.h>
\\int foo(int);
\\int foo(int x) { return x + 1; }
\\int main(int argc, char **argv) {
\\ if (foo(2) != 3) abort();
\\ return 0;
\\}
, "");
cases.add("typedef and function pointer",
\\#include <stdlib.h>
\\typedef struct _Foo Foo;
\\typedef int Ret;
\\typedef int Param;
\\struct _Foo { Ret (*func)(Param p); };
\\static Ret add1(Param p) {
\\ return p + 1;
\\}
\\int main(int argc, char **argv) {
\\ Foo strct = { .func = add1 };
\\ if (strct.func(16) != 17) abort();
\\ return 0;
\\}
, "");
cases.add("ternary operator",
\\#include <stdlib.h>
\\static int cnt = 0;
\\int foo() { cnt++; return 42; }
\\int main(int argc, char **argv) {
\\ short q = 3;
\\ signed char z0 = q?:1;
\\ if (z0 != 3) abort();
\\ int z1 = 3?:1;
\\ if (z1 != 3) abort();
\\ int z2 = foo()?:-1;
\\ if (z2 != 42) abort();
\\ if (cnt != 1) abort();
\\ return 0;
\\}
, "");
cases.add("switch case",
\\#include <stdlib.h>
\\int lottery(unsigned int x) {
\\ switch (x) {
\\ case 3: return 0;
\\ case -1: return 3;
\\ case 8 ... 10: return x;
\\ default: return -1;
\\ }
\\}
\\int main(int argc, char **argv) {
\\ if (lottery(2) != -1) abort();
\\ if (lottery(3) != 0) abort();
\\ if (lottery(-1) != 3) abort();
\\ if (lottery(9) != 9) abort();
\\ return 0;
\\}
, "");
2020-01-05 23:18:26 +00:00
cases.add("boolean values and expressions",
\\#include <stdlib.h>
\\static const _Bool false_val = 0;
\\static const _Bool true_val = 1;
\\void foo(int x, int y) {
\\ _Bool r = x < y;
\\ if (!r) abort();
\\ _Bool self = foo;
\\ if (self == false_val) abort();
\\ if (((r) ? 'a' : 'b') != 'a') abort();
2020-01-05 23:18:26 +00:00
\\}
\\int main(int argc, char **argv) {
\\ foo(2, 5);
\\ if (false_val == true_val) abort();
\\ return 0;
\\}
, "");
cases.add("hello world",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdio.h>
\\int main(int argc, char **argv) {
\\ printf("hello, world!\n");
\\ return 0;
\\}
, "hello, world!" ++ nl);
cases.add("anon struct init",
\\#include <stdlib.h>
\\struct {int a; int b;} x = {1, 2};
\\int main(int argc, char **argv) {
\\ x.a += 2;
\\ x.b += 1;
\\ if (x.a != 3) abort();
\\ if (x.b != 3) abort();
\\ return 0;
\\}
, "");
cases.add("casting away const and volatile",
\\void foo(int *a) {}
\\void bar(const int *a) {
\\ foo((int *)a);
\\}
\\void baz(volatile int *a) {
\\ foo((int *)a);
\\}
\\int main(int argc, char **argv) {
\\ int a = 0;
\\ bar((const int *)&a);
\\ baz((volatile int *)&a);
\\ return 0;
\\}
, "");
cases.add("anonymous struct & unions",
\\#include <stdlib.h>
\\#include <stdint.h>
\\static struct { struct { uint16_t x, y; }; } x = { 1 };
\\static struct { union { uint32_t x; uint8_t y; }; } y = { 0x55AA55AA };
\\int main(int argc, char **argv) {
\\ if (x.x != 1) abort();
\\ if (x.y != 0) abort();
\\ if (y.x != 0x55AA55AA) abort();
\\ if (y.y != 0xAA) abort();
\\ return 0;
\\}
, "");
cases.add("array to pointer decay",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ char data[3] = {'a','b','c'};
\\ if (2[data] != data[2]) abort();
\\ if ("abc"[1] != data[1]) abort();
\\ char *as_ptr = data;
\\ if (2[as_ptr] != as_ptr[2]) abort();
\\ if ("abc"[1] != as_ptr[1]) abort();
\\ return 0;
\\}
, "");
translate-c: packed struct implies align(1) on every field Superceeds PR #12735 (now supporting all packed structs in GNU C) Fixes issue #12733 This stops translating C packed struct as a Zig packed struct. Instead use a regular `extern struct` with `align(1)`. This is because (as @Vexu explained) Zig packed structs are really just integers (not structs). Alignment issue is more complicated. I think @ifreund was the first to notice it in his comment on PR #12735 Justification of my interpretion of the C(lang) behavior comes from a careful reading of the GCC docs for type & variable attributes: (clang emulates gnu's packed attribute here) The final line of the documentation for __attribute__ ((aligned)) [on types] says: > When used on a struct, or struct member, *the aligned attribute can only increase the alignment*; in order to decrease it, the packed attribute must be specified as well. This implies that GCC uses the `packed` attribute for alignment purposes in addition to eliminating padding. The documentation for __attribute__((packed)) [on types], states: > This attribute, attached to a struct, union, or C++ class type definition, specifies that each of its members (other than zero-width bit-fields) is placed to minimize the memory required. **This is equivalent to specifying the packed attribute on each of the members**. The key is resolving this indirection, and looking at the documentation for __attribute__((packed)) [on fields (wierdly under "variables" section)]: > The packed attribute specifies that a **structure member should have the smallest possible alignment** — one bit for a bit-field and one byte otherwise, unless a larger value is specified with the aligned attribute. The attribute does not apply to non-member objects. Furthermore, alignment is the only effect of the packed attribute mentioned in the GCC docs (for "common" architecture). Based on this, it seems safe to completely substitute C 'packed' with Zig 'align(1)'. Target-specific or undocumented behavior potentially changes this. Unfortunately, the current implementation of `translate-c` translates as `packed struct` without alignment info. Because Zig packed structs are really integers (as mentioned above), they are the wrong interpretation and we should be using 'extern struct'. Running `translate-c` on the following code: ```c struct foo { char a; int b; } __attribute__((packed)); struct bar { char a; int b; short c; __attribute__((aligned(8))) long d; } __attribute__((packed)); ``` Previously used a 'packed struct' (which was not FFI-safe on stage1). After applying this change, the translated structures have align(1) explicitly applied to all of their fields AS EXPECTED (unless explicitly overriden). This makes Zig behavior for `tranlsate-c` consistent with clang/GCC. Here is the newly produced (correct) output for the above example: ```zig pub const struct_foo = extern struct { a: u8 align(1), b: c_int align(1), }; pub const struct_bar = extern struct { a: u8 align(1), b: c_int align(1), c: c_short align(1), d: c_long align(8), }; ``` Also note for reference: Since the last stable release (0.9.1), there was a change in the language spec related to the alignment of packed structures. The docs for Zig 0.9.1 read: > Packed structs have 1-byte alignment. So the old behavior of translate-c (not specifying any alignment) was possibly correct back then. However the current docs read: > Packed structs have the same alignment as their backing integer Suggsestive both to the change to an integer-backed representation which is incompatible with C's notation.
2022-09-05 00:16:26 +00:00
cases.add("struct initializer - packed",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdint.h>
\\#include <stdlib.h>
\\struct s {uint8_t x,y;
\\ uint32_t z;} __attribute__((packed)) s0 = {1, 2};
\\int main() {
\\ /* sizeof nor offsetof currently supported */
\\ if (((intptr_t)&s0.z - (intptr_t)&s0.x) != 2) abort();
\\ return 0;
\\}
, "");
cases.add("cast signed array index to unsigned",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int a[10], i = 0;
\\ a[i] = 0;
\\ if (a[i] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("cast long long array index to unsigned",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ long long a[10], i = 0;
\\ a[i] = 0;
\\ if (a[i] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean expression converted to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = 1 + 2 * 3 + 4 * 5 + 6 << 7 | 8 == 9;
\\ if (value != 4224) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean expression on left converted to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = 8 == 9 | 1 + 2 * 3 + 4 * 5 + 6 << 7;
\\ if (value != 4224) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator+ converts bool to int",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) + 3;
\\ int value2 = 3 + (8 == 9);
\\ if (value != value2) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator<",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) < 3;
\\ if (value == 0) abort();
\\ return 0;
\\}
, "");
cases.add("case boolean and operator*",
\\#include <stdlib.h>
\\int main(int argc, char **argv) {
\\ int value = (8 == 9) * 3;
\\ int value2 = 3 * (9 == 9);
\\ if (value != 0) abort();
\\ if (value2 == 0) abort();
\\ return 0;
\\}
, "");
2020-05-03 03:22:43 +00:00
cases.add("scoped typedef",
\\int main(int argc, char **argv) {
\\ typedef int Foo;
\\ typedef Foo Bar;
\\ typedef void (*func)(int);
\\ typedef int uint32_t;
\\ uint32_t a;
2020-05-03 03:22:43 +00:00
\\ Foo i;
\\ Bar j;
\\ return 0;
\\}
, "");
cases.add("scoped for loops with shadowing",
\\#include <stdlib.h>
\\int main() {
\\ int count = 0;
\\ for (int x = 0; x < 2; x++)
\\ for (int x = 0; x < 2; x++)
\\ count++;
\\
\\ if (count != 4) abort();
\\ return 0;
\\}
, "");
cases.add("array value type casts properly",
\\#include <stdlib.h>
\\unsigned int choose[53][10];
\\static int hash_binary(int k)
\\{
\\ choose[0][k] = 3;
\\ int sum = 0;
\\ sum += choose[0][k];
\\ return sum;
\\}
\\
\\int main() {
\\ int s = hash_binary(4);
\\ if (s != 3) abort();
\\ return 0;
\\}
, "");
cases.add("array value type casts properly use +=",
\\#include <stdlib.h>
\\static int hash_binary(int k)
\\{
\\ unsigned int choose[1][1] = {{3}};
\\ int sum = -1;
\\ int prev = 0;
\\ prev = sum += choose[0][0];
\\ if (sum != 2) abort();
\\ return sum + prev;
\\}
\\
\\int main() {
\\ int x = hash_binary(4);
\\ if (x != 4) abort();
\\ return 0;
\\}
, "");
cases.add("ensure array casts outside +=",
\\#include <stdlib.h>
\\static int hash_binary(int k)
\\{
\\ unsigned int choose[3] = {1, 2, 3};
\\ int sum = -2;
\\ int prev = sum + choose[k];
\\ if (prev != 0) abort();
\\ return sum + prev;
\\}
\\
\\int main() {
\\ int x = hash_binary(1);
\\ if (x != -2) abort();
\\ return 0;
\\}
, "");
cases.add("array cast int to uint",
\\#include <stdlib.h>
\\static unsigned int hash_binary(int k)
\\{
\\ int choose[3] = {-1, -2, 3};
\\ unsigned int sum = 2;
\\ sum += choose[k];
\\ return sum;
\\}
\\
\\int main() {
\\ unsigned int x = hash_binary(1);
\\ if (x != 0) abort();
\\ return 0;
\\}
, "");
Support casting enums to all int types. In C, enums are represented as signed integers, so casting from an enum to an integer should use the "cast integer to integer" translation code path. Previously it used the "cast enum to generic non-enum" code path, because enums were not being treated as integers. Ultimately this can produce zig code that fails to compile if the destination type does not support the full range of enum values (e.g. translated C code that casts an enum value to an unsigned integer would fail to compile since enums are signed integers, and unsigned integers cannot represent the full range of values that signed ones can). One interesting thing that came up during testing is that the implicit enum-to-int cast that occurs when an enum is used in a boolean expression was parsed as an (int) by some versions of the zig compiler, and an (unsigned int) cast by others. Specifically, the following code: ```c enum Foo {Bar, Baz}; // ... enum Foo foo = Bar; if (0 || foo) { // do something } ``` When tested on MacOS, Linux, and Windows using a compiler built from the Windows Zig Compiler Dev Kit, the above code would emit a cast to c_uint: `if (false or (@bitCast(c_uint, @enumToInt(foo)) != 0)) {}` However when tested on Windows with a Zig compiler built using MSVC, it produces: `if (false or (@bitCast(c_int, @enumToInt(foo)) != 0)) {}` In this particular case I don't think it matters, since a c_int and c_uint will have the same representation for zero, but I'm not sure if this is ultimately the result of implementation-defined behavior or something else. Because of this, I added explicit casts in the `translate_c.zig` tests, to ensure that the emitted zig source exactly matches across platforms. I also added a behavior test in `run_translated_c.zig` that uses the old implicit casts from `translate_c.zig` to ensure that the emitted Zig code behaves the same as the C code regardless of what cast is used.
2020-12-07 22:20:45 +00:00
cases.add("assign enum to uint, no explicit cast",
\\#include <stdlib.h>
\\typedef enum {
\\ ENUM_0 = 0,
\\ ENUM_1 = 1,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_1;
\\ unsigned int x = val;
\\ if (x != 1) abort();
\\ return 0;
\\}
, "");
cases.add("assign enum to int",
\\#include <stdlib.h>
\\typedef enum {
\\ ENUM_0 = 0,
\\ ENUM_1 = 1,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_1;
\\ int x = val;
\\ if (x != 1) abort();
\\ return 0;
\\}
, "");
cases.add("cast enum to smaller uint",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef enum {
\\ ENUM_0 = 0,
\\ ENUM_257 = 257,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_257;
\\ uint8_t x = (uint8_t)val;
\\ if (x != (uint8_t)257) abort();
\\ return 0;
\\}
, "");
cases.add("cast enum to smaller signed int",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef enum {
\\ ENUM_0 = 0,
\\ ENUM_384 = 384,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_384;
\\ int8_t x = (int8_t)val;
\\ if (x != (int8_t)384) abort();
\\ return 0;
\\}
, "");
cases.add("cast negative enum to smaller signed int",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef enum {
\\ ENUM_MINUS_1 = -1,
\\ ENUM_384 = 384,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_MINUS_1;
\\ int8_t x = (int8_t)val;
\\ if (x != -1) abort();
\\ return 0;
\\}
, "");
cases.add("cast negative enum to smaller unsigned int",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef enum {
\\ ENUM_MINUS_1 = -1,
\\ ENUM_384 = 384,
\\} my_enum_t;
\\
\\int main() {
\\ my_enum_t val = ENUM_MINUS_1;
\\ uint8_t x = (uint8_t)val;
\\ if (x != (uint8_t)-1) abort();
\\ return 0;
\\}
, "");
cases.add("implicit enum cast in boolean expression",
\\#include <stdlib.h>
\\enum Foo {
\\ FooA,
\\ FooB,
\\ FooC,
\\};
\\int main() {
\\ int a = 0;
\\ float b = 0;
\\ void *c = 0;
\\ enum Foo d = FooA;
\\ if (a || d) abort();
\\ if (d && b) abort();
\\ if (c || d) abort();
\\ return 0;
\\}
, "");
cases.add("issue #6707 cast builtin call result to opaque struct pointer",
\\#include <stdlib.h>
\\struct foo* make_foo(void)
\\{
\\ return (struct foo*)__builtin_strlen("0123456789ABCDEF");
\\}
\\int main(void) {
\\ struct foo *foo_pointer = make_foo();
\\ if (foo_pointer != (struct foo*)16) abort();
\\ return 0;
\\}
, "");
cases.add("C built-ins",
\\#include <stdlib.h>
\\#include <limits.h>
\\#include <stdbool.h>
\\#define M_E 2.71828182845904523536
\\#define M_PI_2 1.57079632679489661923
\\bool check_clz(unsigned int pos) {
\\ return (__builtin_clz(1 << pos) == (8 * sizeof(unsigned int) - pos - 1));
\\}
\\int main(void) {
\\ if (__builtin_bswap16(0x0102) != 0x0201) abort();
\\ if (__builtin_bswap32(0x01020304) != 0x04030201) abort();
\\ if (__builtin_bswap64(0x0102030405060708) != 0x0807060504030201) abort();
\\
\\ if (__builtin_signbit(0.0) != 0) abort();
\\ if (__builtin_signbitf(0.0f) != 0) abort();
\\ if (__builtin_signbit(1.0) != 0) abort();
\\ if (__builtin_signbitf(1.0f) != 0) abort();
\\ if (__builtin_signbit(-1.0) != 1) abort();
\\ if (__builtin_signbitf(-1.0f) != 1) abort();
\\
\\ if (__builtin_popcount(0) != 0) abort();
\\ if (__builtin_popcount(0b1) != 1) abort();
\\ if (__builtin_popcount(0b11) != 2) abort();
\\ if (__builtin_popcount(0b1111) != 4) abort();
\\ if (__builtin_popcount(0b11111111) != 8) abort();
\\
\\ if (__builtin_ctz(0b1) != 0) abort();
\\ if (__builtin_ctz(0b10) != 1) abort();
\\ if (__builtin_ctz(0b100) != 2) abort();
\\ if (__builtin_ctz(0b10000) != 4) abort();
\\ if (__builtin_ctz(0b100000000) != 8) abort();
\\
\\ if (!check_clz(0)) abort();
\\ if (!check_clz(1)) abort();
\\ if (!check_clz(2)) abort();
\\ if (!check_clz(4)) abort();
\\ if (!check_clz(8)) abort();
\\
\\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256))) != 2.0) abort();
\\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0))) != 2.0) abort();
\\ if (__builtin_sqrt(__builtin_sqrt(__builtin_sqrt(256.0f))) != 2.0) abort();
\\ if (__builtin_sqrtf(__builtin_sqrtf(__builtin_sqrtf(256.0f))) != 2.0f) abort();
\\
\\ if (__builtin_sin(1.0) != -__builtin_sin(-1.0)) abort();
\\ if (__builtin_sinf(1.0f) != -__builtin_sinf(-1.0f)) abort();
\\ if (__builtin_sin(M_PI_2) != 1.0) abort();
\\ if (__builtin_sinf(M_PI_2) != 1.0f) abort();
\\
\\ if (__builtin_cos(1.0) != __builtin_cos(-1.0)) abort();
\\ if (__builtin_cosf(1.0f) != __builtin_cosf(-1.0f)) abort();
\\ if (__builtin_cos(0.0) != 1.0) abort();
\\ if (__builtin_cosf(0.0f) != 1.0f) abort();
\\
\\ if (__builtin_exp(0) != 1.0) abort();
\\ if (__builtin_fabs(__builtin_exp(1.0) - M_E) > 0.00000001) abort();
\\ if (__builtin_exp(0.0f) != 1.0f) abort();
\\
\\ if (__builtin_exp2(0) != 1.0) abort();
\\ if (__builtin_exp2(4.0) != 16.0) abort();
\\ if (__builtin_exp2f(0.0f) != 1.0f) abort();
\\ if (__builtin_exp2f(4.0f) != 16.0f) abort();
\\
\\ if (__builtin_log(M_E) != 1.0) abort();
\\ if (__builtin_log(1.0) != 0.0) abort();
\\ if (__builtin_logf(1.0f) != 0.0f) abort();
\\
\\ if (__builtin_log2(8.0) != 3.0) abort();
\\ if (__builtin_log2(1.0) != 0.0) abort();
\\ if (__builtin_log2f(8.0f) != 3.0f) abort();
\\ if (__builtin_log2f(1.0f) != 0.0f) abort();
\\
\\ if (__builtin_log10(1000.0) != 3.0) abort();
\\ if (__builtin_log10(1.0) != 0.0) abort();
\\ if (__builtin_log10f(1000.0f) != 3.0f) abort();
\\ if (__builtin_log10f(1.0f) != 0.0f) abort();
\\
\\ if (__builtin_fabs(-42.0f) != 42.0) abort();
\\ if (__builtin_fabs(-42.0) != 42.0) abort();
\\ if (__builtin_fabs(-42) != 42.0) abort();
\\ if (__builtin_fabsf(-42.0f) != 42.0f) abort();
\\
\\ if (__builtin_fabs(-42.0f) != 42.0) abort();
\\ if (__builtin_fabs(-42.0) != 42.0) abort();
\\ if (__builtin_fabs(-42) != 42.0) abort();
\\ if (__builtin_fabsf(-42.0f) != 42.0f) abort();
\\
\\ if (__builtin_abs(42) != 42) abort();
\\ if (__builtin_abs(-42) != 42) abort();
\\ if (__builtin_abs(INT_MIN) != INT_MIN) abort();
\\
\\ if (__builtin_floor(42.9) != 42.0) abort();
\\ if (__builtin_floor(-42.9) != -43.0) abort();
\\ if (__builtin_floorf(42.9f) != 42.0f) abort();
\\ if (__builtin_floorf(-42.9f) != -43.0f) abort();
\\
\\ if (__builtin_ceil(42.9) != 43.0) abort();
\\ if (__builtin_ceil(-42.9) != -42) abort();
\\ if (__builtin_ceilf(42.9f) != 43.0f) abort();
\\ if (__builtin_ceilf(-42.9f) != -42.0f) abort();
\\
\\ if (__builtin_trunc(42.9) != 42.0) abort();
\\ if (__builtin_truncf(42.9f) != 42.0f) abort();
\\ if (__builtin_trunc(-42.9) != -42.0) abort();
\\ if (__builtin_truncf(-42.9f) != -42.0f) abort();
\\
\\ if (__builtin_round(0.5) != 1.0) abort();
\\ if (__builtin_round(-0.5) != -1.0) abort();
\\ if (__builtin_roundf(0.5f) != 1.0f) abort();
\\ if (__builtin_roundf(-0.5f) != -1.0f) abort();
\\
\\ if (__builtin_strcmp("abc", "abc") != 0) abort();
\\ if (__builtin_strcmp("abc", "def") >= 0 ) abort();
\\ if (__builtin_strcmp("def", "abc") <= 0) abort();
\\
\\ if (__builtin_strlen("this is a string") != 16) abort();
\\
\\ char *s = malloc(6);
\\ __builtin_memcpy(s, "hello", 5);
\\ s[5] = '\0';
\\ if (__builtin_strlen(s) != 5) abort();
\\
\\ __builtin_memset(s, 42, __builtin_strlen(s));
\\ if (s[0] != 42 || s[1] != 42 || s[2] != 42 || s[3] != 42 || s[4] != 42) abort();
\\
\\ free(s);
\\
\\ return 0;
\\}
, "");
cases.add("function macro that uses builtin",
\\#include <stdlib.h>
\\#define FOO(x, y) (__builtin_popcount((x)) + __builtin_strlen((y)))
\\int main() {
\\ if (FOO(7, "hello!") != 9) abort();
\\ return 0;
\\}
, "");
cases.add("assign bool result to int or char",
\\#include <stdlib.h>
\\#include <stdbool.h>
\\bool foo() { return true; }
\\int main() {
\\ int x = foo();
\\ if (x != 1) abort();
\\ signed char c = foo();
\\ if (c != 1) abort();
\\ return 0;
\\}
, "");
cases.add("static K&R-style no prototype function declaration (empty parameter list)",
\\#include <stdlib.h>
\\static int foo() {
\\ return 42;
\\}
\\int main() {
\\ if (foo() != 42) abort();
\\ return 0;
\\}
, "");
cases.add("K&R-style static function prototype for unused function",
\\static int foo();
\\int main() {
\\ return 0;
\\}
, "");
cases.add("K&R-style static function prototype + separate definition",
\\#include <stdlib.h>
\\static int foo();
\\static int foo(int a, int b) {
\\ return a + b;
\\}
\\int main() {
\\ if (foo(40, 2) != 42) abort();
\\ return 0;
\\}
, "");
cases.add("dollar sign in identifiers",
\\#include <stdlib.h>
\\#define $FOO 2
\\#define $foo bar$
\\#define $baz($x) ($x + $FOO)
\\int $$$(int $x$) { return $x$ + $FOO; }
\\int main() {
\\ int bar$ = 42;
\\ if ($foo != 42) abort();
\\ if (bar$ != 42) abort();
\\ if ($baz(bar$) != 44) abort();
\\ if ($$$(bar$) != 44) abort();
\\ return 0;
\\}
, "");
cases.add("Cast boolean expression result to int",
\\#include <stdlib.h>
\\char foo(char c) { return c; }
\\int bar(int i) { return i; }
\\long baz(long l) { return l; }
\\int main() {
\\ if (foo(1 == 2)) abort();
\\ if (!foo(1 == 1)) abort();
\\ if (bar(1 == 2)) abort();
\\ if (!bar(1 == 1)) abort();
\\ if (baz(1 == 2)) abort();
\\ if (!baz(1 == 1)) abort();
\\ return 0;
\\}
, "");
cases.add("Wide, UTF-16, and UTF-32 character literals",
\\#include <wchar.h>
\\#include <stdlib.h>
\\int main() {
\\ wchar_t wc = L'™';
\\ int utf16_char = u'™';
\\ int utf32_char = U'💯';
\\ if (wc != 8482) abort();
\\ if (utf16_char != 8482) abort();
\\ if (utf32_char != 128175) abort();
\\ unsigned char c = wc;
\\ if (c != 0x22) abort();
\\ c = utf32_char;
\\ if (c != 0xaf) abort();
\\ return 0;
\\}
, "");
cases.add("Variadic function call",
\\#define _NO_CRT_STDIO_INLINE 1
\\#include <stdio.h>
\\int main(void) {
\\ printf("%d %d\n", 1, 2);
\\ return 0;
\\}
, "1 2" ++ nl);
cases.add("multi-character character constant",
\\#include <stdlib.h>
\\int main(void) {
\\ int foo = 'abcd';
\\ switch (foo) {
\\ case 'abcd': break;
\\ default: abort();
\\ }
\\ return 0;
\\}
, "");
cases.add("Array initializers (string literals, incomplete arrays)",
\\#include <stdlib.h>
\\#include <string.h>
\\extern int foo[];
\\int global_arr[] = {1, 2, 3};
\\char global_string[] = "hello";
\\int main(int argc, char *argv[]) {
\\ if (global_arr[2] != 3) abort();
\\ if (strlen(global_string) != 5) abort();
\\ const char *const_str = "hello";
\\ if (strcmp(const_str, "hello") != 0) abort();
\\ char empty_str[] = "";
\\ if (strlen(empty_str) != 0) abort();
\\ char hello[] = "hello";
\\ if (strlen(hello) != 5 || sizeof(hello) != 6) abort();
\\ int empty[] = {};
\\ if (sizeof(empty) != 0) abort();
\\ int bar[] = {42};
\\ if (bar[0] != 42) abort();
\\ bar[0] = 43;
\\ if (bar[0] != 43) abort();
\\ int baz[] = {1, [42] = 123, 456};
\\ if (baz[42] != 123 || baz[43] != 456) abort();
\\ if (sizeof(baz) != sizeof(int) * 44) abort();
\\ const char *const names[] = {"first", "second", "third"};
\\ if (strcmp(names[2], "third") != 0) abort();
\\ char catted_str[] = "abc" "def";
\\ if (strlen(catted_str) != 6 || sizeof(catted_str) != 7) abort();
\\ char catted_trunc_str[2] = "abc" "def";
\\ if (sizeof(catted_trunc_str) != 2 || catted_trunc_str[0] != 'a' || catted_trunc_str[1] != 'b') abort();
\\ char big_array_utf8lit[10] = "💯";
\\ if (strcmp(big_array_utf8lit, "💯") != 0 || big_array_utf8lit[9] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("Wide, UTF-16, and UTF-32 string literals",
\\#include <stdlib.h>
\\#include <stdint.h>
\\#include <wchar.h>
\\int main(void) {
\\ const wchar_t *wide_str = L"wide";
\\ const wchar_t wide_hello[] = L"hello";
\\ if (wcslen(wide_str) != 4) abort();
\\ if (wcslen(L"literal") != 7) abort();
\\ if (wcscmp(wide_hello, L"hello") != 0) abort();
\\
\\ const uint16_t *u16_str = u"wide";
\\ const uint16_t u16_hello[] = u"hello";
\\ if (u16_str[3] != u'e' || u16_str[4] != 0) abort();
\\ if (u16_hello[4] != u'o' || u16_hello[5] != 0) abort();
\\
\\ const uint32_t *u32_str = U"wide";
\\ const uint32_t u32_hello[] = U"hello";
\\ if (u32_str[3] != U'e' || u32_str[4] != 0) abort();
\\ if (u32_hello[4] != U'o' || u32_hello[5] != 0) abort();
\\ return 0;
\\}
, "");
cases.add("Address of function is no-op",
\\#include <stdlib.h>
\\#include <stdbool.h>
\\typedef int (*myfunc)(int);
\\int a(int arg) { return arg + 1;}
\\int b(int arg) { return arg + 2;}
\\int caller(myfunc fn, int arg) {
\\ return fn(arg);
\\}
\\int main() {
\\ myfunc arr[3] = {&a, &b, a};
\\ myfunc foo = a;
\\ myfunc bar = &(a);
\\ if (foo != bar) abort();
\\ if (arr[0] == arr[1]) abort();
\\ if (arr[0] != arr[2]) abort();
\\ if (caller(b, 40) != 42) abort();
\\ if (caller(&b, 40) != 42) abort();
\\ return 0;
\\}
, "");
2022-10-10 16:17:52 +00:00
cases.add("Obscure ways of calling functions; issue #4124",
\\#include <stdlib.h>
\\static int add(int a, int b) {
\\ return a + b;
\\}
\\typedef int (*adder)(int, int);
\\typedef void (*funcptr)(void);
\\int main() {
\\ if ((add)(1, 2) != 3) abort();
\\ if ((&add)(1, 2) != 3) abort();
\\ if (add(3, 1) != 4) abort();
\\ if ((*add)(2, 3) != 5) abort();
\\ if ((**add)(7, -1) != 6) abort();
\\ if ((***add)(-2, 9) != 7) abort();
\\
\\ int (*ptr)(int a, int b);
\\ ptr = add;
\\
\\ if (ptr(1, 2) != 3) abort();
\\ if ((*ptr)(3, 1) != 4) abort();
\\ if ((**ptr)(2, 3) != 5) abort();
\\ if ((***ptr)(7, -1) != 6) abort();
\\ if ((****ptr)(-2, 9) != 7) abort();
\\
\\ funcptr addr1 = (funcptr)(add);
\\ funcptr addr2 = (funcptr)(&add);
\\
\\ if (addr1 != addr2) abort();
\\ if (((int(*)(int, int))addr1)(1, 2) != 3) abort();
\\ if (((adder)addr2)(1, 2) != 3) abort();
\\ return 0;
\\}
, "");
cases.add("Return boolean expression as int; issue #6215",
\\#include <stdlib.h>
\\#include <stdbool.h>
\\bool actual_bool(void) { return 4 - 1 < 4;}
\\char char_bool_ret(void) { return 0 || 1; }
\\short short_bool_ret(void) { return 0 < 1; }
\\int int_bool_ret(void) { return 1 && 1; }
\\long long_bool_ret(void) { return !(0 > 1); }
\\static int GLOBAL = 1;
\\int nested_scopes(int a, int b) {
\\ if (a == 1) {
\\ int target = 1;
\\ return b == target;
\\ } else {
\\ int target = 2;
\\ if (b == target) {
\\ return GLOBAL == 1;
\\ }
\\ return target == 2;
\\ }
\\}
\\int main(void) {
\\ if (!actual_bool()) abort();
\\ if (!char_bool_ret()) abort();
\\ if (!short_bool_ret()) abort();
\\ if (!int_bool_ret()) abort();
\\ if (!long_bool_ret()) abort();
\\ if (!nested_scopes(1, 1)) abort();
\\ if (nested_scopes(1, 2)) abort();
\\ if (!nested_scopes(0, 2)) abort();
\\ if (!nested_scopes(0, 3)) abort();
\\ return 1 != 1;
\\}
, "");
cases.add("Comma operator should create new scope; issue #7989",
\\#include <stdlib.h>
\\#include <stdio.h>
\\int main(void) {
\\ if (1 || (abort(), 1)) {}
\\ if (0 && (1, printf("do not print\n"))) {}
\\ int x = 0;
\\ x = (x = 3, 4, x + 1);
\\ if (x != 4) abort();
\\ return 0;
\\}
, "");
cases.add("Use correct break label for statement expression in nested scope",
\\#include <stdlib.h>
\\int main(void) {
\\ int x = ({1, ({2; 3;});});
\\ if (x != 3) abort();
\\ return 0;
\\}
, "");
cases.add("pointer difference: scalar array w/ size truncation or negative result. Issue #7216",
\\#include <stdlib.h>
\\#include <stddef.h>
\\#define SIZE 10
\\int main() {
\\ int foo[SIZE];
\\ int *start = &foo[0];
\\ int *one_past_end = start + SIZE;
\\ ptrdiff_t diff = one_past_end - start;
\\ char diff_char = one_past_end - start;
\\ if (diff != SIZE || diff_char != SIZE) abort();
\\ diff = start - one_past_end;
\\ if (diff != -SIZE) abort();
\\ if (one_past_end - foo != SIZE) abort();
\\ if ((one_past_end - 1) - foo != SIZE - 1) abort();
\\ if ((start + 1) - foo != 1) abort();
\\ return 0;
\\}
, "");
// C standard: if the expression P points either to an element of an array object or one
// past the last element of an array object, and the expression Q points to the last
// element of the same array object, the expression ((Q)+1)-(P) has the same value as
// ((Q)-(P))+1 and as -((P)-((Q)+1)), and has the value zero if the expression P points
// one past the last element of the array object, even though the expression (Q)+1
// does not point to an element of the array object
cases.add("pointer difference: C standard edge case",
\\#include <stdlib.h>
\\#include <stddef.h>
\\#define SIZE 10
\\int main() {
\\ int foo[SIZE];
\\ int *start = &foo[0];
\\ int *P = start + SIZE;
\\ int *Q = &foo[SIZE - 1];
\\ if ((Q + 1) - P != 0) abort();
\\ if ((Q + 1) - P != (Q - P) + 1) abort();
\\ if ((Q + 1) - P != -(P - (Q + 1))) abort();
\\ return 0;
\\}
, "");
cases.add("pointer difference: unary operators",
\\#include <stdlib.h>
\\int main() {
\\ int foo[10];
\\ int *x = &foo[1];
\\ const int *y = &foo[5];
\\ if (y - x++ != 4) abort();
\\ if (y - x != 3) abort();
\\ if (y - ++x != 2) abort();
\\ if (y - x-- != 2) abort();
\\ if (y - x != 3) abort();
\\ if (y - --x != 4) abort();
\\ if (y - &foo[0] != 5) abort();
\\ return 0;
\\}
, "");
cases.add("pointer difference: struct array with padding",
\\#include <stdlib.h>
\\#include <stddef.h>
\\#define SIZE 10
\\typedef struct my_struct {
\\ int x;
\\ char c;
\\ int y;
\\} my_struct_t;
\\int main() {
\\ my_struct_t foo[SIZE];
\\ my_struct_t *start = &foo[0];
\\ my_struct_t *one_past_end = start + SIZE;
\\ ptrdiff_t diff = one_past_end - start;
\\ int diff_int = one_past_end - start;
\\ if (diff != SIZE || diff_int != SIZE) abort();
\\ diff = start - one_past_end;
\\ if (diff != -SIZE) abort();
\\ return 0;
\\}
, "");
cases.add("pointer difference: array of function pointers",
\\#include <stdlib.h>
\\int a(void) { return 1;}
\\int b(void) { return 2;}
\\int c(void) { return 3;}
\\typedef int (*myfunc)(void);
\\int main() {
\\ myfunc arr[] = {a, b, c, a, b, c};
\\ myfunc *f1 = &arr[1];
\\ myfunc *f4 = &arr[4];
\\ if (f4 - f1 != 3) abort();
\\ return 0;
\\}
, "");
2021-02-24 07:25:11 +00:00
cases.add("typeof operator",
\\#include <stdlib.h>
\\static int FOO = 42;
\\typedef typeof(FOO) foo_type;
\\typeof(foo_type) myfunc(typeof(FOO) x) { return (typeof(FOO)) x; }
\\int main(void) {
\\ int x = FOO;
\\ typeof(x) y = x;
\\ foo_type z = y;
\\ if (x != y) abort();
\\ if (myfunc(z) != x) abort();
\\
\\ const char *my_string = "bar";
\\ typeof (typeof (my_string)[4]) string_arr = {"a","b","c","d"};
\\ if (string_arr[0][0] != 'a' || string_arr[3][0] != 'd') abort();
\\ return 0;
\\}
, "");
cases.add("offsetof",
\\#include <stddef.h>
\\#include <stdlib.h>
\\#define container_of(ptr, type, member) ({ \
\\ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
\\ (type *)( (char *)__mptr - offsetof(type,member) );})
\\typedef struct {
\\ int i;
\\ struct { int x; char y; int z; } s;
\\ float f;
\\} container;
\\int main(void) {
\\ if (offsetof(container, i) != 0) abort();
\\ if (offsetof(container, s) <= offsetof(container, i)) abort();
\\ if (offsetof(container, f) <= offsetof(container, s)) abort();
\\
\\ container my_container;
\\ typeof(my_container.s) *inner_member_pointer = &my_container.s;
\\ float *float_member_pointer = &my_container.f;
\\ int *anon_member_pointer = &my_container.s.z;
\\ container *my_container_p;
\\
\\ my_container_p = container_of(inner_member_pointer, container, s);
\\ if (my_container_p != &my_container) abort();
\\
\\ my_container_p = container_of(float_member_pointer, container, f);
\\ if (my_container_p != &my_container) abort();
\\
\\ if (container_of(anon_member_pointer, typeof(my_container.s), z) != inner_member_pointer) abort();
\\ return 0;
\\}
, "");
cases.add("handle assert.h",
\\#include <assert.h>
\\int main() {
\\ int x = 1;
\\ int *xp = &x;
\\ assert(1);
\\ assert(x != 0);
\\ assert(xp);
\\ assert(*xp);
\\ return 0;
\\}
, "");
cases.add("NDEBUG disables assert",
\\#define NDEBUG
\\#include <assert.h>
\\int main() {
\\ assert(0);
\\ assert(NULL);
\\ return 0;
\\}
, "");
cases.add("pointer arithmetic with signed operand",
\\#include <stdlib.h>
\\int main() {
\\ int array[10];
\\ int *x = &array[5];
\\ int *y;
\\ int idx = 0;
\\ y = x + ++idx;
\\ if (y != x + 1 || y != &array[6]) abort();
\\ y = idx + x;
\\ if (y != x + 1 || y != &array[6]) abort();
\\ y = x - idx;
\\ if (y != x - 1 || y != &array[4]) abort();
\\
\\ idx = 0;
\\ y = --idx + x;
\\ if (y != x - 1 || y != &array[4]) abort();
\\ y = idx + x;
\\ if (y != x - 1 || y != &array[4]) abort();
\\ y = x - idx;
\\ if (y != x + 1 || y != &array[6]) abort();
\\
\\ idx = 1;
\\ x += idx;
\\ if (x != &array[6]) abort();
\\ x -= idx;
\\ if (x != &array[5]) abort();
\\ y = (x += idx);
\\ if (y != x || y != &array[6]) abort();
\\ y = (x -= idx);
\\ if (y != x || y != &array[5]) abort();
\\
\\ if (array + idx != &array[1] || array + 1 != &array[1]) abort();
\\ idx = -1;
\\ if (array - idx != &array[1]) abort();
\\
\\ return 0;
\\}
, "");
cases.add("Compound literals",
\\#include <stdlib.h>
\\struct Foo {
\\ int a;
\\ char b[2];
\\ float c;
\\};
\\int main() {
\\ struct Foo foo;
\\ int x = 1, y = 2;
\\ foo = (struct Foo) {x + y, {'a', 'b'}, 42.0f};
\\ if (foo.a != x + y || foo.b[0] != 'a' || foo.b[1] != 'b' || foo.c != 42.0f) abort();
\\ return 0;
\\}
, "");
cases.add("Generic selections",
\\#include <stdlib.h>
\\#include <string.h>
\\#include <stdint.h>
\\#define my_generic_fn(X) _Generic((X), \
\\ int: abs, \
\\ char *: strlen, \
\\ size_t: malloc, \
\\ default: free \
\\)(X)
\\#define my_generic_val(X) _Generic((X), \
\\ int: 1, \
\\ const char *: "bar" \
\\)
\\int main(void) {
\\ if (my_generic_val(100) != 1) abort();
\\
\\ const char *foo = "foo";
\\ const char *bar = my_generic_val(foo);
\\ if (strcmp(bar, "bar") != 0) abort();
\\
\\ if (my_generic_fn(-42) != 42) abort();
\\ if (my_generic_fn("hello") != 5) abort();
\\
\\ size_t size = 8192;
\\ uint8_t *mem = my_generic_fn(size);
\\ memset(mem, 42, size);
\\ if (mem[size - 1] != 42) abort();
\\ my_generic_fn(mem);
\\
\\ return 0;
\\}
, "");
// See __builtin_alloca_with_align comment in std.zig.c_builtins
cases.add("use of unimplemented builtin in unused function does not prevent compilation",
\\#include <stdlib.h>
\\void unused() {
\\ __builtin_alloca_with_align(1, 8);
\\}
\\int main(void) {
\\ if (__builtin_sqrt(1.0) != 1.0) abort();
\\ return 0;
\\}
, "");
cases.add("convert single-statement bodies into blocks for if/else/for/while. issue #8159",
\\#include <stdlib.h>
\\int foo() { return 1; }
\\int main(void) {
\\ int i = 0;
\\ if (i == 0) if (i == 0) if (i != 0) i = 1;
\\ if (i != 0) i = 1; else if (i == 0) if (i == 0) i += 1;
\\ for (; i < 10;) for (; i < 10;) i++;
\\ while (i == 100) while (i == 100) foo();
\\ if (0) do do "string"; while(1); while(1);
\\ return 0;
\\}
, "");
cases.add("cast RHS of compound assignment if necessary, unused result",
\\#include <stdlib.h>
\\int main(void) {
\\ signed short val = -1;
\\ val += 1; if (val != 0) abort();
\\ val -= 1; if (val != -1) abort();
\\ val *= 2; if (val != -2) abort();
\\ val /= 2; if (val != -1) abort();
\\ val %= 2; if (val != -1) abort();
\\ val <<= 1; if (val != -2) abort();
\\ val >>= 1; if (val != -1) abort();
\\ val += 100000000; // compile error if @truncate() not inserted
\\ unsigned short uval = 1;
\\ uval += 1; if (uval != 2) abort();
\\ uval -= 1; if (uval != 1) abort();
\\ uval *= 2; if (uval != 2) abort();
\\ uval /= 2; if (uval != 1) abort();
\\ uval %= 2; if (uval != 1) abort();
\\ uval <<= 1; if (uval != 2) abort();
\\ uval >>= 1; if (uval != 1) abort();
\\ uval += 100000000; // compile error if @truncate() not inserted
\\}
, "");
cases.add("cast RHS of compound assignment if necessary, used result",
\\#include <stdlib.h>
\\int main(void) {
\\ signed short foo;
\\ signed short val = -1;
\\ foo = (val += 1); if (foo != 0) abort();
\\ foo = (val -= 1); if (foo != -1) abort();
\\ foo = (val *= 2); if (foo != -2) abort();
\\ foo = (val /= 2); if (foo != -1) abort();
\\ foo = (val %= 2); if (foo != -1) abort();
\\ foo = (val <<= 1); if (foo != -2) abort();
\\ foo = (val >>= 1); if (foo != -1) abort();
\\ foo = (val += 100000000); // compile error if @truncate() not inserted
\\ unsigned short ufoo;
\\ unsigned short uval = 1;
\\ ufoo = (uval += 1); if (ufoo != 2) abort();
\\ ufoo = (uval -= 1); if (ufoo != 1) abort();
\\ ufoo = (uval *= 2); if (ufoo != 2) abort();
\\ ufoo = (uval /= 2); if (ufoo != 1) abort();
\\ ufoo = (uval %= 2); if (ufoo != 1) abort();
\\ ufoo = (uval <<= 1); if (ufoo != 2) abort();
\\ ufoo = (uval >>= 1); if (ufoo != 1) abort();
\\ ufoo = (uval += 100000000); // compile error if @truncate() not inserted
\\}
, "");
cases.add("basic vector expressions",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
\\int main(int argc, char**argv) {
\\ __v8hi uninitialized;
\\ __v8hi empty_init = {};
\\ for (int i = 0; i < 8; i++) {
\\ if (empty_init[i] != 0) abort();
\\ }
\\ __v8hi partial_init = {0, 1, 2, 3};
\\
\\ __v8hi a = {0, 1, 2, 3, 4, 5, 6, 7};
\\ __v8hi b = (__v8hi) {100, 200, 300, 400, 500, 600, 700, 800};
\\
\\ __v8hi sum = a + b;
\\ for (int i = 0; i < 8; i++) {
\\ if (sum[i] != a[i] + b[i]) abort();
\\ }
\\ return 0;
\\}
, "");
cases.add("__builtin_shufflevector",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef int16_t __v4hi __attribute__((__vector_size__(8)));
\\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
\\int main(int argc, char**argv) {
\\ __v8hi v8_a = {0, 1, 2, 3, 4, 5, 6, 7};
\\ __v8hi v8_b = {100, 200, 300, 400, 500, 600, 700, 800};
\\ __v8hi shuffled = __builtin_shufflevector(v8_a, v8_b, 0, 1, 2, 3, 8, 9, 10, 11);
\\ for (int i = 0; i < 8; i++) {
\\ if (i < 4) {
\\ if (shuffled[i] != v8_a[i]) abort();
\\ } else {
\\ if (shuffled[i] != v8_b[i - 4]) abort();
\\ }
\\ }
\\ shuffled = __builtin_shufflevector(
\\ (__v8hi) {-1, -1, -1, -1, -1, -1, -1, -1},
\\ (__v8hi) {42, 42, 42, 42, 42, 42, 42, 42},
\\ 0, 1, 2, 3, 8, 9, 10, 11
\\ );
\\ for (int i = 0; i < 8; i++) {
\\ if (i < 4) {
\\ if (shuffled[i] != -1) abort();
\\ } else {
\\ if (shuffled[i] != 42) abort();
\\ }
\\ }
\\ __v4hi shuffled_to_fewer_elements = __builtin_shufflevector(v8_a, v8_b, 0, 1, 8, 9);
\\ for (int i = 0; i < 4; i++) {
\\ if (i < 2) {
\\ if (shuffled_to_fewer_elements[i] != v8_a[i]) abort();
\\ } else {
\\ if (shuffled_to_fewer_elements[i] != v8_b[i - 2]) abort();
\\ }
\\ }
\\ __v4hi v4_a = {0, 1, 2, 3};
\\ __v4hi v4_b = {100, 200, 300, 400};
\\ __v8hi shuffled_to_more_elements = __builtin_shufflevector(v4_a, v4_b, 0, 1, 2, 3, 4, 5, 6, 7);
\\ for (int i = 0; i < 4; i++) {
\\ if (shuffled_to_more_elements[i] != v4_a[i]) abort();
\\ if (shuffled_to_more_elements[i + 4] != v4_b[i]) abort();
\\ }
\\ return 0;
\\}
, "");
cases.add("__builtin_convertvector",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef int16_t __v8hi __attribute__((__vector_size__(16)));
\\typedef uint16_t __v8hu __attribute__((__vector_size__(16)));
\\int main(int argc, char**argv) {
\\ __v8hi signed_vector = { 1, 2, 3, 4, -1, -2, -3,-4};
\\ __v8hu unsigned_vector = __builtin_convertvector(signed_vector, __v8hu);
\\
\\ for (int i = 0; i < 8; i++) {
\\ if (unsigned_vector[i] != (uint16_t)signed_vector[i]) abort();
\\ }
\\ return 0;
\\}
, "");
cases.add("vector casting",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef int8_t __v8qi __attribute__((__vector_size__(8)));
\\typedef uint8_t __v8qu __attribute__((__vector_size__(8)));
\\int main(int argc, char**argv) {
\\ __v8qi signed_vector = { 1, 2, 3, 4, -1, -2, -3,-4};
\\
\\ uint64_t big_int = (uint64_t) signed_vector;
\\ if (big_int != 0x01020304FFFEFDFCULL && big_int != 0xFCFDFEFF04030201ULL) abort();
\\ __v8qu unsigned_vector = (__v8qu) big_int;
\\ for (int i = 0; i < 8; i++) {
\\ if (unsigned_vector[i] != (uint8_t)signed_vector[i] && unsigned_vector[i] != (uint8_t)signed_vector[7 - i]) abort();
\\ }
\\ return 0;
\\}
, "");
cases.add("break from switch statement. Issue #8387",
\\#include <stdlib.h>
\\int switcher(int x) {
\\ switch (x) {
\\ case 0: // no braces
\\ x += 1;
\\ break;
\\ case 1: // conditional break
\\ if (x == 1) {
\\ x += 1;
\\ break;
\\ }
\\ x += 100;
\\ case 2: { // braces with fallthrough
\\ x += 1;
\\ }
\\ case 3: // fallthrough to return statement
\\ x += 1;
\\ case 42: { // random out of order case
\\ x += 1;
\\ return x;
\\ }
\\ case 4: { // break within braces
\\ x += 1;
\\ break;
\\ }
\\ case 5:
\\ x += 1; // fallthrough to default
\\ default:
\\ x += 1;
\\ }
\\ return x;
\\}
\\int main(void) {
\\ int expected[] = {1, 2, 5, 5, 5, 7, 7};
\\ for (int i = 0; i < sizeof(expected) / sizeof(int); i++) {
\\ int res = switcher(i);
\\ if (res != expected[i]) abort();
\\ }
\\ if (switcher(42) != 43) abort();
\\ return 0;
\\}
, "");
cases.add("Cast to enum from larger integral type. Issue #6011",
\\#include <stdint.h>
\\#include <stdlib.h>
\\enum Foo { A, B, C };
\\static inline enum Foo do_stuff(void) {
\\ int64_t i = 1;
\\ return (enum Foo)i;
\\}
\\int main(void) {
\\ if (do_stuff() != B) abort();
\\ return 0;
\\}
, "");
cases.add("Render array LHS as grouped node if necessary",
\\#include <stdlib.h>
\\int main(void) {
\\ int arr[] = {40, 41, 42, 43};
\\ if ((arr + 1)[1] != 42) abort();
\\ return 0;
\\}
, "");
cases.add("typedef with multiple names",
\\#include <stdlib.h>
\\typedef struct {
\\ char field;
\\} a_t, b_t;
\\
\\int main(void) {
\\ a_t a = { .field = 42 };
\\ b_t b = a;
\\ if (b.field != 42) abort();
\\ return 0;
\\}
, "");
cases.add("__cleanup__ attribute",
\\#include <stdlib.h>
\\static int cleanup_count = 0;
\\void clean_up(int *final_value) {
\\ if (*final_value != cleanup_count++) abort();
\\}
\\void doit(void) {
\\ int a __attribute__ ((__cleanup__(clean_up))) __attribute__ ((unused)) = 2;
\\ int b __attribute__ ((__cleanup__(clean_up))) __attribute__ ((unused)) = 1;
\\ int c __attribute__ ((__cleanup__(clean_up))) __attribute__ ((unused)) = 0;
\\}
\\int main(void) {
\\ doit();
\\ if (cleanup_count != 3) abort();
\\ return 0;
\\}
, "");
cases.add("enum used as boolean expression",
\\#include <stdlib.h>
\\enum FOO {BAR, BAZ};
\\int main(void) {
\\ enum FOO x = BAR;
\\ if (x) abort();
\\ if (!BAZ) abort();
\\ return 0;
\\}
, "");
cases.add("Flexible arrays",
\\#include <stdlib.h>
\\#include <stdint.h>
\\typedef struct { char foo; int bar; } ITEM;
\\typedef struct { size_t count; ITEM items[]; } ITEM_LIST;
\\typedef struct { unsigned char count; int items[]; } INT_LIST;
\\#define SIZE 10
\\int main(void) {
\\ ITEM_LIST *list = malloc(sizeof(ITEM_LIST) + SIZE * sizeof(ITEM));
\\ for (int i = 0; i < SIZE; i++) list->items[i] = (ITEM) {.foo = i, .bar = i + 1};
\\ const ITEM_LIST *const c_list = list;
\\ for (int i = 0; i < SIZE; i++) if (c_list->items[i].foo != i || c_list->items[i].bar != i + 1) abort();
\\ INT_LIST *int_list = malloc(sizeof(INT_LIST) + SIZE * sizeof(int));
\\ for (int i = 0; i < SIZE; i++) int_list->items[i] = i;
\\ const INT_LIST *const c_int_list = int_list;
\\ const int *const ints = int_list->items;
\\ for (int i = 0; i < SIZE; i++) if (ints[i] != i) abort();
\\ return 0;
\\}
, "");
cases.add("Flexible array with typedefed flexible item, issue #16838",
\\#include <stdlib.h>
\\#include <assert.h>
\\typedef int MARKER[0];
\\typedef struct { int x; MARKER y; } Flexible;
\\#define SIZE 10
\\int main(void) {
\\ Flexible *flex = malloc(sizeof(Flexible) + SIZE * sizeof(int));
\\ for (int i = 0; i < SIZE; i++) {
\\ flex->y[i] = i;
\\ }
\\ for (int i = 0; i < SIZE; i++) {
\\ assert(flex->y[i] == i);
\\ }
\\ return 0;
\\}
, "");
cases.add("enum with value that fits in c_uint but not c_int, issue #8003",
\\#include <stdlib.h>
\\enum my_enum {
\\ FORCE_UINT = 0xffffffff
\\};
\\int main(void) {
\\ if(FORCE_UINT != 0xffffffff) abort();
\\}
, "");
cases.add("block-scope static variable shadows function parameter. Issue #8208",
\\#include <stdlib.h>
\\int func1(int foo) { return foo + 1; }
\\int func2(void) {
\\ static int foo = 5;
\\ return foo++;
\\}
\\int main(void) {
\\ if (func1(42) != 43) abort();
\\ if (func2() != 5) abort();
\\ if (func2() != 6) abort();
\\ return 0;
\\}
, "");
cases.add("nested same-name static locals",
\\#include <stdlib.h>
\\int func(int val) {
\\ static int foo;
\\ if (foo != val) abort();
\\ {
\\ foo += 1;
\\ static int foo = 2;
\\ if (foo != val + 2) abort();
\\ foo += 1;
\\ }
\\ return foo;
\\}
\\int main(void) {
\\ int foo = 1;
\\ if (func(0) != 1) abort();
\\ if (func(1) != 2) abort();
\\ if (func(2) != 3) abort();
\\ if (foo != 1) abort();
\\ return 0;
\\}
, "");
cases.add("Enum constants are assigned correct type. Issue #9153",
\\enum A { A0, A1=0xFFFFFFFF };
\\enum B { B0=-1, B1=0xFFFFFFFF };
\\enum C { C0=-1, C1=0 };
\\enum D { D0, D1=0xFFFFFFFFFFL };
\\enum E { E0=-1, E1=0xFFFFFFFFFFL };
\\int main(void) {
\\ signed char a0 = A0, a1 = A1;
\\ signed char b0 = B0, b1 = B1;
\\ signed char c0 = C0, c1 = C1;
\\ signed char d0 = D0, d1 = D1;
\\ signed char e0 = E0, e1 = E1;
\\ return 0;
\\}
, "");
cases.add("Enum constant matches enum name; multiple enumerations with same value",
\\#include <stdlib.h>
\\enum FOO {
\\ FOO = 1,
\\ BAR = 2,
\\ BAZ = 1,
\\};
\\int main(void) {
\\ enum FOO x = BAZ;
\\ if (x != 1) abort();
\\ if (x != BAZ) abort();
\\ if (x != FOO) abort();
\\}
, "");
cases.add("Scoped enums",
\\#include <stdlib.h>
\\int main(void) {
\\ enum Foo { A, B, C };
\\ enum Foo a = B;
\\ if (a != B) abort();
\\ if (a != 1) abort();
\\ {
\\ enum Foo { A = 5, B = 6, C = 7 };
\\ enum Foo a = B;
\\ if (a != B) abort();
\\ if (a != 6) abort();
\\ }
\\ if (a != B) abort();
\\ if (a != 1) abort();
\\}
, "");
cases.add("Underscore identifiers",
\\#include <stdlib.h>
\\int _ = 10;
\\typedef struct { int _; } S;
\\int main(void) {
\\ if (_ != 10) abort();
\\ S foo = { ._ = _ };
\\ if (foo._ != _) abort();
\\ return 0;
\\}
, "");
cases.add("__builtin_choose_expr (unchosen expression is not evaluated)",
\\#include <stdlib.h>
\\int main(void) {
\\ int x = 0.0;
\\ int y = 0.0;
\\ int res;
\\ res = __builtin_choose_expr(1, 1, x / y);
\\ if (res != 1) abort();
\\ res = __builtin_choose_expr(0, x / y, 2);
\\ if (res != 2) abort();
\\ return 0;
\\}
, "");
// TODO: add isnan check for long double once bitfield support is added
// (needed for x86_64-windows-gnu)
// TODO: add isinf check for long double once std.math.isInf supports c_longdouble
cases.add("NAN and INFINITY",
\\#include <math.h>
\\#include <stdint.h>
\\#include <stdlib.h>
\\union uf { uint32_t u; float f; };
\\#define CHECK_NAN(STR, VAL) { \
\\ union uf unpack = {.f = __builtin_nanf(STR)}; \
\\ if (!isnan(unpack.f)) abort(); \
\\ if (unpack.u != VAL) abort(); \
\\}
\\int main(void) {
\\ float f_nan = NAN;
\\ if (!isnan(f_nan)) abort();
\\ double d_nan = NAN;
\\ if (!isnan(d_nan)) abort();
\\ CHECK_NAN("0", 0x7FC00000);
\\ CHECK_NAN("", 0x7FC00000);
\\ CHECK_NAN("1", 0x7FC00001);
\\ CHECK_NAN("0x7FC00000", 0x7FC00000);
\\ CHECK_NAN("0x7FC0000F", 0x7FC0000F);
\\ CHECK_NAN("0x7FC000F0", 0x7FC000F0);
\\ CHECK_NAN("0x7FC00F00", 0x7FC00F00);
\\ CHECK_NAN("0x7FC0F000", 0x7FC0F000);
\\ CHECK_NAN("0x7FCF0000", 0x7FCF0000);
\\ CHECK_NAN("0xFFFFFFFF", 0x7FFFFFFF);
\\ float f_inf = INFINITY;
\\ if (!isinf(f_inf)) abort();
\\ double d_inf = INFINITY;
\\ if (!isinf(d_inf)) abort();
\\ return 0;
\\}
, "");
cases.add("signed array subscript. Issue #8556",
\\#include <stdint.h>
\\#include <stdlib.h>
\\#define TEST_NEGATIVE(type) { type x = -1; if (ptr[x] != 42) abort(); }
\\#define TEST_UNSIGNED(type) { type x = 2; if (arr[x] != 42) abort(); }
\\int main(void) {
\\ int arr[] = {40, 41, 42, 43};
\\ int *ptr = arr + 3;
\\ if (ptr[-1] != 42) abort();
\\ TEST_NEGATIVE(int);
\\ TEST_NEGATIVE(long);
\\ TEST_NEGATIVE(long long);
\\ TEST_NEGATIVE(int64_t);
\\ TEST_NEGATIVE(__int128);
\\ TEST_UNSIGNED(unsigned);
\\ TEST_UNSIGNED(unsigned long);
\\ TEST_UNSIGNED(unsigned long long);
\\ TEST_UNSIGNED(uint64_t);
\\ TEST_UNSIGNED(size_t);
\\ TEST_UNSIGNED(unsigned __int128);
\\ return 0;
\\}
, "");
cases.add("Ensure side-effects only evaluated once for signed array indices",
\\#include <stdlib.h>
\\int main(void) {
\\ int foo[] = {1, 2, 3, 4};
\\ int *p = foo;
\\ int idx = 1;
\\ if ((++p)[--idx] != 2) abort();
\\ if (p != foo + 1) abort();
\\ if (idx != 0) abort();
\\ if ((p++)[idx++] != 2) abort();
\\ if (p != foo + 2) abort();
\\ if (idx != 1) abort();
\\ return 0;
\\}
, "");
cases.add("Allow non-const char* string literals. Issue #9126",
\\#include <stdlib.h>
\\int func(char *x) { return x[0]; }
\\struct S { char *member; };
\\struct S global_struct = { .member = "global" };
\\char *g = "global";
\\int main(void) {
\\ if (g[0] != 'g') abort();
\\ if (global_struct.member[0] != 'g') abort();
\\ char *string = "hello";
\\ if (string[0] != 'h') abort();
\\ struct S s = {.member = "hello"};
\\ if (s.member[0] != 'h') abort();
\\ if (func("foo") != 'f') abort();
\\ return 0;
\\}
, "");
cases.add("Ensure while loop under an if doesn't steal the else. Issue #9953",
\\#include <stdio.h>
\\void doWork(int id) { }
\\int reallyDelete(int id) { printf("deleted %d\n", id); return 1; }
\\int process(int id, int n, int delete) {
\\ if(!delete)
\\ while(n-- > 0) doWork(id);
\\ else
\\ return reallyDelete(id);
\\ return 0;
\\}
\\int main(void) {
\\ process(99, 3, 0);
\\ return 0;
\\}
, "");
cases.add("Remainder operator with negative integers. Issue #10176",
\\#include <stdlib.h>
\\int main(void) {
\\ int denominator = -2;
\\ int numerator = 5;
\\ if (numerator % denominator != 1) abort();
\\ numerator = -5; denominator = 2;
\\ if (numerator % denominator != -1) abort();
\\ return 0;
\\}
, "");
cases.add("Boolean expression coerced to int. Issue #10175",
\\#include <stdlib.h>
\\int sign(int v) {
\\ return -(v < 0);
\\}
\\int main(void) {
\\ if (sign(-5) != -1) abort();
\\ if (sign(5) != 0) abort();
\\ if (sign(0) != 0) abort();
\\ return 0;
\\}
, "");
cases.add("Typedef'ed void used as return type. Issue #10356",
\\typedef void V;
\\V foo(V *f) {}
\\int main(void) {
\\ int x = 0;
\\ foo(&x);
\\ return 0;
\\}
, "");
cases.add("Zero-initialization of global union. Issue #10797",
\\#include <stdlib.h>
\\union U { int x; double y; };
\\union U u;
\\int main(void) {
\\ if (u.x != 0) abort();
\\ return 0;
\\}
, "");
cases.add("Cast-to-union. Issue #10955",
\\#include <stdlib.h>
\\struct S { int x; };
\\union U {
\\ long l;
\\ double d;
\\ struct S s;
\\};
\\union U bar(union U u) { return u; }
\\int main(void) {
\\ union U u = (union U) 42L;
\\ if (u.l != 42L) abort();
\\ u = (union U) 2.0;
\\ if (u.d != 2.0) abort();
\\ u = bar((union U)4.0);
\\ if (u.d != 4.0) abort();
\\ u = (union U)(struct S){ .x = 5 };
\\ if (u.s.x != 5) abort();
\\ return 0;
\\}
, "");
cases.add("Nested comma operator in macro. Issue #11040",
\\#include <stdlib.h>
\\#define FOO (1, (2, 3))
\\int main(void) {
\\ int x = FOO;
\\ if (x != 3) abort();
\\ return 0;
\\}
, "");
// The C standard does not require function pointers to be convertible to any integer type.
// However, POSIX requires that function pointers have the same representation as `void *`
// so that dlsym() can work
cases.add("Function to integral",
\\#include <stdint.h>
\\int main(void) {
\\#if defined(__UINTPTR_MAX__) && __has_include(<unistd.h>)
\\ uintptr_t x = (uintptr_t)main;
\\#endif
\\ return 0;
\\}
, "");
cases.add("Closure over local in typeof",
\\#include <stdlib.h>
\\int main(void) {
\\ int x = 123;
\\ union { typeof(x) val; } u = { x };
\\ if (u.val != 123) abort();
\\ return 0;
\\}
, "");
cases.add("struct without global declaration does not conflict with local variable name",
\\#include <stdlib.h>
\\static void foo(struct foobar *unused) {}
\\int main(void) {
\\ int struct_foobar = 123;
\\ if (struct_foobar != 123) abort();
\\ int foobar = 456;
\\ if (foobar != 456) abort();
\\ return 0;
\\}
, "");
cases.add("struct without global declaration does not conflict with global variable name",
\\#include <stdlib.h>
\\static void foo(struct foobar *unused) {}
\\static int struct_foobar = 123;
\\static int foobar = 456;
\\int main(void) {
\\ if (struct_foobar != 123) abort();
\\ if (foobar != 456) abort();
\\ return 0;
\\}
, "");
}