Improved variable length array support.
authorJames Lyon <jamesly0n@hotmail.com>
Sat, 27 Apr 2013 19:39:34 +0000 (27 20:39 +0100)
committerJames Lyon <jamesly0n@hotmail.com>
Sat, 27 Apr 2013 21:58:52 +0000 (27 22:58 +0100)
VLA storage is now freed when it goes out of scope. This makes it
possible to use a VLA inside a loop without consuming an unlimited
amount of memory.

Combining VLAs with alloca() should work as in GCC - when a VLA is
freed, memory allocated by alloca() after the VLA was created is also
freed. There are some exceptions to this rule when using goto: if a VLA
is in scope at the goto, jumping to a label will reset the stack pointer
to where it was immediately after the last VLA was created prior to the
label, or to what it was before the first VLA was created if the label
is outside the scope of any VLA. This means that in some cases combining
alloca() and VLAs will free alloca() memory where GCC would not.

12 files changed:
CMakeLists.txt
arm-gen.c
c67-gen.c
config.h.in
configure
i386-gen.c
tcc.h
tccgen.c
tests/CMakeLists.txt
tests/Makefile
tests/vla_test.c [new file with mode: 0644]
x86_64-gen.c

index 41dd81b..fb0d968 100644 (file)
@@ -57,6 +57,8 @@ endif()
 # Use two variables to keep CMake configuration variable names consistent
 set(TCC_BCHECK OFF CACHE BOOL "Enable bounds checking")
 set(CONFIG_TCC_BCHECK ${TCC_BCHECK})
+set(TCC_ASSERT OFF CACHE BOOL "Enable assertions")
+set(CONFIG_TCC_ASSERT ${TCC_ASSERT})
 
 set(TCC_BUILD_NATIVE ON CACHE BOOL "Build native compiler")
 set(TCC_BUILD_I386 OFF CACHE BOOL "Build i386 cross compiler")
index 0f70062..a883121 100644 (file)
--- a/arm-gen.c
+++ b/arm-gen.c
@@ -2017,6 +2017,21 @@ void ggoto(void)
   vtop--;
 }
 
+/* Save the stack pointer onto the stack and return the location of its address */
+ST_FUNC void gen_vla_sp_save(int addr) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
+/* Restore the SP from a location on the stack */
+ST_FUNC void gen_vla_sp_restore(int addr) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
+/* Subtract from the stack pointer, and push the resulting value onto the stack */
+ST_FUNC void gen_vla_alloc(CType *type, int align) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
 /* end of ARM code generator */
 /*************************************************************/
 #endif
index 7d559c8..0d5e33f 100644 (file)
--- a/c67-gen.c
+++ b/c67-gen.c
@@ -2560,6 +2560,21 @@ void ggoto(void)
     vtop--;
 }
 
+/* Save the stack pointer onto the stack and return the location of its address */
+ST_FUNC void gen_vla_sp_save(int addr) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
+/* Restore the SP from a location on the stack */
+ST_FUNC void gen_vla_sp_restore(int addr) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
+/* Subtract from the stack pointer, and push the resulting value onto the stack */
+ST_FUNC void gen_vla_alloc(CType *type, int align) {
+    tcc_error("variable length arrays unsupported for this target");
+}
+
 /* end of C67 code generator */
 /*************************************************************/
 #endif
index 16393e9..c9e1a18 100644 (file)
@@ -3,4 +3,5 @@
 
 #cmakedefine CONFIG_WIN32
 #cmakedefine CONFIG_WIN64
-#cmakedefine CONFIG_TCC_BCHECK
\ No newline at end of file
+#cmakedefine CONFIG_TCC_BCHECK
+#cmakedefine CONFIG_TCC_ASSERT
index 72d48af..b513cc1 100755 (executable)
--- a/configure
+++ b/configure
@@ -19,6 +19,7 @@ TMPH=$TMPN.h
 # default parameters
 build_cross="no"
 use_libgcc="no"
+enable_assert="no"
 prefix=""
 execprefix=""
 bindir=""
@@ -165,6 +166,8 @@ for opt do
   ;;
   --enable-cross) build_cross="yes"
   ;;
+  --enable-assert) enable_assert="yes"
+  ;;
   --disable-static) disable_static="yes"
   ;;
   --disable-rpath) disable_rpath="yes"
@@ -274,6 +277,7 @@ Advanced options (experts only):
   --enable-mingw32         build windows version on linux with mingw32
   --enable-cygwin          build windows version on windows with cygwin
   --enable-cross           build cross compilers
+  --enable-assert          enable debug assertions
   --with-selinux           use mmap for exec mem [needs writable /tmp]
   --sysincludepaths=...    specify system include paths, colon separated
   --libpaths=...           specify system library paths, colon separated
@@ -484,6 +488,9 @@ if test "$have_selinux" = "yes" ; then
   echo "#define HAVE_SELINUX" >> $TMPH
   echo "HAVE_SELINUX=yes" >> config.mak
 fi
+if test "$enable_assert" = "yes" ; then
+  echo "#define CONFIG_TCC_ASSERT" >> $TMPH
+fi
 
 version=`head $source_path/VERSION`
 echo "VERSION=$version" >>config.mak
index 044f9e6..9ff2e18 100644 (file)
@@ -43,6 +43,7 @@ enum {
     TREG_ECX,
     TREG_EDX,
     TREG_ST0,
+    TREG_ESP = 4
 };
 
 /* return registers for function */
@@ -1117,6 +1118,44 @@ ST_FUNC void gen_bounded_ptr_deref(void)
 }
 #endif
 
+/* Save the stack pointer onto the stack */
+ST_FUNC void gen_vla_sp_save(int addr) {
+    /* mov %esp,addr(%ebp)*/
+    o(0x89);
+    gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr);
+}
+
+/* Restore the SP from a location on the stack */
+ST_FUNC void gen_vla_sp_restore(int addr) {
+    o(0x8b);
+    gen_modrm(TREG_ESP, VT_LOCAL, NULL, addr);
+}
+
+/* Subtract from the stack pointer, and push the resulting value onto the stack */
+ST_FUNC void gen_vla_alloc(CType *type, int align) {
+#ifdef TCC_TARGET_PE
+    /* alloca does more than just adjust %rsp on Windows */
+    vpush_global_sym(&func_old_type, TOK_alloca);
+    vswap(); /* Move alloca ref past allocation size */
+    gfunc_call(1);
+    vset(type, REG_IRET, 0);
+#else
+    int r;
+    r = gv(RC_INT); /* allocation size */
+    /* sub r,%rsp */
+    o(0x2b);
+    o(0xe0 | r);
+    /* We align to 16 bytes rather than align */
+    /* and ~15, %esp */
+    o(0xf0e483);
+    /* mov %esp, r */
+    o(0x89);
+    o(0xe0 | r);
+    vpop();
+    vset(type, r, 0);
+#endif
+}
+
 /* end of X86 code generator */
 /*************************************************************/
 #endif
diff --git a/tcc.h b/tcc.h
index 7205f32..859d4fd 100644 (file)
--- a/tcc.h
+++ b/tcc.h
 #include <setjmp.h>
 #include <time.h>
 
+#ifdef CONFIG_TCCASSERT
+#include <assert.h>
+#define TCC_ASSERT(ex) assert(ex)
+#else
+#define TCC_ASSERT(ex)
+#endif
+
 #ifndef _WIN32
 # include <unistd.h>
 # include <sys/time.h>
@@ -404,6 +411,12 @@ typedef struct AttributeDef {
 #define SYM_FIELD      0x20000000 /* struct/union field symbol space */
 #define SYM_FIRST_ANOM 0x10000000 /* first anonymous sym */
 
+#define VLA_SP_LOC_SET     0x01 /* Location of SP on stack has been allocated */
+#define VLA_SP_SAVED       0x02 /* SP has been saved to slot already */
+#define VLA_NEED_NEW_FRAME 0x04 /* Needs new frame for next VLA */
+#define VLA_IN_SCOPE       0x08 /* One or more VLAs are in scope */
+#define VLA_SCOPE_FLAGS    (VLA_SP_SAVED|VLA_NEED_NEW_FRAME|VLA_IN_SCOPE) /* Flags which are saved and restored upon entering and exiting a block */
+
 /* stored in 'Sym.c' field */
 #define FUNC_NEW       1 /* ansi function prototype */
 #define FUNC_OLD       2 /* old function prototype */
@@ -1201,7 +1214,7 @@ ST_FUNC void decl(int l);
 #if defined CONFIG_TCC_BCHECK || defined TCC_TARGET_C67
 ST_FUNC Sym *get_sym_ref(CType *type, Section *sec, unsigned long offset, unsigned long size);
 #endif
-#ifdef TCC_TARGET_X86_64
+#if defined TCC_TARGET_X86_64 && !defined TCC_TARGET_PE
 ST_FUNC int classify_x86_64_va_arg(CType *ty);
 #endif
 
@@ -1286,6 +1299,9 @@ ST_FUNC void o(unsigned int c);
 #ifndef TCC_TARGET_ARM
 ST_FUNC void gen_cvt_itof(int t);
 #endif
+ST_FUNC void gen_vla_sp_save(int addr);
+ST_FUNC void gen_vla_sp_restore(int addr);
+ST_FUNC void gen_vla_alloc(CType *type, int align);
 
 /* ------------ i386-gen.c ------------ */
 #if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64
index 0d1e21b..c5c41de 100644 (file)
--- a/tccgen.c
+++ b/tccgen.c
@@ -55,6 +55,11 @@ ST_DATA Sym *define_stack;
 ST_DATA Sym *global_label_stack;
 ST_DATA Sym *local_label_stack;
 
+ST_DATA int vla_sp_loc_tmp; /* vla_sp_loc is set to this when the value won't be needed later */
+ST_DATA int vla_sp_root_loc; /* vla_sp_loc for SP before any VLAs were pushed */
+ST_DATA int *vla_sp_loc; /* Pointer to variable holding location to store stack pointer on the stack when modifying stack pointer */
+ST_DATA int vla_flags; /* VLA_* flags */
+
 ST_DATA SValue __vstack[1+VSTACK_SIZE], *vtop;
 
 ST_DATA int const_wanted; /* true if constant wanted */
@@ -81,6 +86,7 @@ static int decl0(int l, int is_for_loop_init);
 static void expr_eq(void);
 static void unary_type(CType *type);
 static void vla_runtime_type_size(CType *type, int *a);
+static void vla_sp_save(void);
 static int is_compatible_parameter_types(CType *type1, CType *type2);
 static void expr_type(CType *type);
 
@@ -2109,6 +2115,17 @@ ST_FUNC void vla_runtime_type_size(CType *type, int *a)
     }
 }
 
+static void vla_sp_save(void) {
+    if (!(vla_flags & VLA_SP_LOC_SET)) {
+        *vla_sp_loc = (loc -= PTR_SIZE);
+        vla_flags |= VLA_SP_LOC_SET;
+    }
+    if (!(vla_flags & VLA_SP_SAVED)) {
+        gen_vla_sp_save(*vla_sp_loc);
+        vla_flags |= VLA_SP_SAVED;
+    }
+}
+
 /* return the pointed type of t */
 static inline CType *pointed_type(CType *type)
 {
@@ -4484,6 +4501,16 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
         frame_bottom->next = scope_stack_bottom;
         scope_stack_bottom = frame_bottom;
         llabel = local_label_stack;
+        
+        /* save VLA state */
+        int block_vla_sp_loc = *vla_sp_loc;
+        int *saved_vla_sp_loc = vla_sp_loc;
+        if (saved_vla_sp_loc != &vla_sp_root_loc)
+          vla_sp_loc = &block_vla_sp_loc;
+
+        int saved_vla_flags = vla_flags;
+        vla_flags |= VLA_NEED_NEW_FRAME;
+        
         /* handle local labels declarations */
         if (tok == TOK_LABEL) {
             next();
@@ -4527,6 +4554,16 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
         /* pop locally defined symbols */
         scope_stack_bottom = scope_stack_bottom->next;
         sym_pop(&local_stack, s);
+        
+        /* Pop VLA frames and restore stack pointer if required */
+        if (saved_vla_sp_loc != &vla_sp_root_loc)
+            *saved_vla_sp_loc = block_vla_sp_loc;
+        if (vla_sp_loc != (saved_vla_sp_loc == &vla_sp_root_loc ? &vla_sp_root_loc : &block_vla_sp_loc)) {
+            vla_sp_loc = saved_vla_sp_loc;
+            gen_vla_sp_restore(*vla_sp_loc);
+        }
+        vla_flags = (vla_flags & ~VLA_SCOPE_FLAGS) | (saved_vla_flags & VLA_SCOPE_FLAGS);
+        
         next();
     } else if (tok == TOK_RETURN) {
         next();
@@ -4731,6 +4768,13 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
                     s->r = LABEL_FORWARD;
             }
             /* label already defined */
+            if (vla_flags & VLA_IN_SCOPE) {
+                /* If VLAs are in use, save the current stack pointer and
+                   reset the stack pointer to what it was at function entry
+                   (label will restore stack pointer in inner scopes) */
+                vla_sp_save();
+                gen_vla_sp_restore(vla_sp_root_loc);
+            }
             if (s->r & LABEL_FORWARD) 
                 s->jnext = gjmp(s->jnext);
             else
@@ -4746,6 +4790,12 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
         b = is_label();
         if (b) {
             /* label case */
+            if (vla_flags & VLA_IN_SCOPE) {
+                /* save/restore stack pointer across label
+                   this is a no-op when combined with the load immediately
+                   after the label unless we arrive via goto */
+                vla_sp_save();
+            }
             s = label_find(b);
             if (s) {
                 if (s->r == LABEL_DEFINED)
@@ -4756,6 +4806,10 @@ static void block(int *bsym, int *csym, int *case_sym, int *def_sym,
                 s = label_push(&global_label_stack, b, LABEL_DEFINED);
             }
             s->jnext = ind;
+            if (vla_flags & VLA_IN_SCOPE) {
+                gen_vla_sp_restore(*vla_sp_loc);
+                vla_flags |= VLA_NEED_NEW_FRAME;
+            }
             /* we accept this, but it is a mistake */
         block_after_label:
             if (tok == '}') {
@@ -5026,24 +5080,21 @@ static void decl_initializer(CType *type, Section *sec, unsigned long c,
     CType *t1;
 
     if (type->t & VT_VLA) {
-#if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64
         int a;
-        CValue retcval;
-
-        vpush_global_sym(&func_old_type, TOK_alloca);
+        
+        /* save current stack pointer */
+        if (vla_flags & VLA_NEED_NEW_FRAME) {
+            vla_sp_save();
+            vla_flags = VLA_IN_SCOPE;
+            vla_sp_loc = &vla_sp_loc_tmp;
+        }
+        
         vla_runtime_type_size(type, &a);
-        gfunc_call(1);
-
-        /* return value */
-        retcval.i = 0;
-        vsetc(type, REG_IRET, &retcval);
+        gen_vla_alloc(type, a);
         vset(type, VT_LOCAL|VT_LVAL, c);
         vswap();
         vstore();
         vpop();
-#else
-        tcc_error("variable length arrays unsupported for this target");
-#endif
     } else if (type->t & VT_ARRAY) {
         s = type->ref;
         n = s->c;
@@ -5585,6 +5636,9 @@ static void gen_function(Sym *sym)
     put_extern_sym(sym, cur_text_section, ind, 0);
     funcname = get_tok_str(sym->v, NULL);
     func_ind = ind;
+    /* Initialize VLA state */
+    vla_sp_loc = &vla_sp_root_loc;
+    vla_flags = VLA_NEED_NEW_FRAME;
     /* put debug symbol */
     if (tcc_state->do_debug)
         put_func_debug(sym);
index bcd37cd..cf5caf8 100644 (file)
@@ -15,11 +15,16 @@ add_test(NAME abitest-cc WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND abitest-c
 
 set(ABITEST_TCC abitest-tcc${CMAKE_EXECUTABLE_SUFFIX})
 get_property(LIBTCC_LIB TARGET libtcc PROPERTY LOCATION)
-add_custom_command(OUTPUT ${ABITEST_TCC} COMMAND tcc ${TCC_CFLAGS} -g ${CMAKE_CURRENT_SOURCE_DIR}/abitest.c ${LIBTCC_LDFLAGS} ${LIBTCC_LIB} -o ${ABITEST_TCC} DEPENDS tcc abitest.c)
+add_custom_command(OUTPUT ${ABITEST_TCC} COMMAND tcc ${TCC_CFLAGS} -g ${CMAKE_CURRENT_SOURCE_DIR}/abitest.c ${LIBTCC_LDFLAGS} ${LIBTCC_LIB} -o ${ABITEST_TCC} DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/abitest.c)
 add_custom_target(abitest-tcc-exe ALL DEPENDS ${ABITEST_TCC})
 
 add_test(NAME abitest-tcc WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${ABITEST_TCC} lib_path=${CMAKE_BINARY_DIR} include=${CMAKE_SOURCE_DIR}/include)
 
+set(VLA_TEST vla_test${CMAKE_EXECUTABLE_SUFFIX})
+add_custom_command(OUTPUT ${VLA_TEST} COMMAND tcc ${TCC_CFLAGS} -g ${CMAKE_CURRENT_SOURCE_DIR}/vla_test.c -o ${VLA_TEST} DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/vla_test.c)
+add_custom_target(vla_test-exe ALL DEPENDS ${VLA_TEST})
+add_test(vla_test vla_test)
+
 add_executable(tcctest-cc tcctest.c)
 target_link_libraries(tcctest-cc libtcc)
 set_target_properties(tcctest-cc PROPERTIES COMPILE_FLAGS -std=gnu99)
@@ -41,21 +46,21 @@ if(PYTHONINTERP_FOUND)
 
   # Object + link output
   set(TEST4 test4${CMAKE_EXECUTABLE_SUFFIX})
-  add_custom_command(OUTPUT test4.o COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -c -o test4.o DEPENDS tcc tcctest.c)
+  add_custom_command(OUTPUT test4.o COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -c -o test4.o DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c)
   add_custom_command(OUTPUT ${TEST4} COMMAND tcc ${TCC_TEST_CFLAGS} test4.o -o ${TEST4} DEPENDS tcc test4.o)
   add_custom_target(test4-exe ALL DEPENDS ${TEST4})
   add_test(test4 ${TCCTEST_PY} ${CMAKE_CURRENT_BINARY_DIR}/${TEST4})
 
   # Dynamic output
   set(TEST5 test5${CMAKE_EXECUTABLE_SUFFIX})
-  add_custom_command(OUTPUT ${TEST5} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -o ${TEST5} DEPENDS tcc tcctest.c)
+  add_custom_command(OUTPUT ${TEST5} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -o ${TEST5} DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c)
   add_custom_target(test5-exe ALL DEPENDS ${TEST5})
   add_test(test5 ${TCCTEST_PY} ${CMAKE_CURRENT_BINARY_DIR}/${TEST5})
 
   if(TCC_BCHECK)
     # Dynamic output + bound check
     set(TEST6 test6${CMAKE_EXECUTABLE_SUFFIX})
-    add_custom_command(OUTPUT ${TEST5} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -b -o ${TEST6} DEPENDS tcc tcctest.c)
+    add_custom_command(OUTPUT ${TEST6} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -b -o ${TEST6} DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c)
     add_custom_target(test6-exe ALL DEPENDS ${TEST6})
     add_test(test6 ${TCCTEST_PY} ${CMAKE_CURRENT_BINARY_DIR}/${TEST6})
   endif()
@@ -63,7 +68,7 @@ if(PYTHONINTERP_FOUND)
   if(0)
     # Static output
     set(TEST7 test7${CMAKE_EXECUTABLE_SUFFIX})
-    add_custom_command(OUTPUT ${TEST7} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -static -o ${TEST7} DEPENDS tcc tcctest.c)
+    add_custom_command(OUTPUT ${TEST7} COMMAND tcc ${TCC_TEST_CFLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c -static -o ${TEST7} DEPENDS tcc ${CMAKE_CURRENT_SOURCE_DIR}/tcctest.c)
     add_custom_target(test7-exe ALL DEPENDS ${TEST7})
     add_test(test7 ${TCCTEST_PY} ${CMAKE_CURRENT_BINARY_DIR}/${TEST7})
   endif()
index edb581c..08dfa42 100644 (file)
@@ -14,6 +14,7 @@ TESTS = \
  libtest \
  test3 \
  abitest \
+ vla_test-run \
  moretests
 
 # test4 -- problem with -static
@@ -31,6 +32,10 @@ endif
 ifeq ($(TARGETOS),Darwin)
  TESTS := $(filter-out hello-exe test3 btest,$(TESTS))
 endif
+ifeq ($(ARCH),i386)
+else ifneq ($(ARCH),x86-64)
+ TESTS := $(filter-out vla_test-run,$(TESTS))
+endif
 
 ifdef DISABLE_STATIC
  export LD_LIBRARY_PATH:=$(CURDIR)/..
@@ -190,6 +195,12 @@ abitest: abitest-cc$(EXESUF) abitest-tcc$(EXESUF)
        ./abitest-cc$(EXESUF) lib_path=.. include="$(top_srcdir)/include"
        ./abitest-tcc$(EXESUF) lib_path=.. include="$(top_srcdir)/include"
 
+vla_test$(EXESUF): vla_test.c
+       $(TCC) -o $@ $^ $(CPPFLAGS) $(CFLAGS)
+vla_test-run: vla_test$(EXESUF)
+       @echo ------------ $@ ------------
+       ./vla_test$(EXESUF)
+
 # targets for development
 %.bin: %.c tcc
        $(TCC) -g -o $@ $<
diff --git a/tests/vla_test.c b/tests/vla_test.c
new file mode 100644 (file)
index 0000000..3616c46
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Test that allocating a variable length array in a loop
+ * does not use up a linear amount of memory
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define LOOP_COUNT 1000
+#define ARRAY_SIZE 100
+
+/* Overwrite a VLA. This will overwrite the return address if SP is incorrect */
+void smash(char *p, int n) {
+  memset(p, 0, n);
+}
+
+int test1(int n) {
+  int i;
+  char *array_ptrs[LOOP_COUNT];
+  
+  for (i = 0; i < LOOP_COUNT; ++i) {
+    char test[n];
+    smash(test, n);
+    array_ptrs[i] = test;
+  }
+  
+  return (array_ptrs[0]-array_ptrs[LOOP_COUNT-1] < n) ? 0 : 1;
+}
+
+/* ensure goto does not circumvent array free */
+int test2(int n) {
+  char *array_ptrs[LOOP_COUNT];
+
+  int i = 0;
+loop:;
+  char test[n];
+  smash(test, n);
+  if (i >= LOOP_COUNT)
+    goto end;
+  array_ptrs[i] = test;
+  ++i;
+  goto loop;
+
+end:
+  smash(test, n);
+  char test2[n];
+  smash(test2, n);
+  return (array_ptrs[0] - array_ptrs[LOOP_COUNT-1] < n) ? 0 : 1;
+}
+
+int test3(int n) {
+  char test[n];
+  smash(test, n);
+  goto label;
+label:
+  smash(test, n);
+  char test2[n];
+  smash(test2, n);
+  return (test-test2 >= n) ? 0 : 1;
+}
+
+#define RUN_TEST(t) \
+  if (!testname || (strcmp(#t, testname) == 0)) { \
+    fputs(#t "... ", stdout); \
+    fflush(stdout); \
+    if (t(ARRAY_SIZE) == 0) { \
+      fputs("success\n", stdout); \
+    } else { \
+      fputs("failure\n", stdout); \
+      retval = EXIT_FAILURE; \
+    } \
+  }
+
+int main(int argc, char **argv) {
+  const char *testname = NULL;
+  int retval = EXIT_SUCCESS;
+  if (argc > 1)
+    testname = argv[1];
+  RUN_TEST(test1)
+  RUN_TEST(test2)
+  RUN_TEST(test3)
+  return retval;
+}
index db24cdd..24fa2c6 100644 (file)
@@ -23,7 +23,7 @@
 #ifdef TARGET_DEFS_ONLY
 
 /* number of available registers */
-#define NB_REGS         24
+#define NB_REGS         25
 #define NB_ASM_REGS     8
 
 /* a register can belong to several classes. The classes must be
@@ -57,6 +57,7 @@ enum {
     TREG_RAX = 0,
     TREG_RCX = 1,
     TREG_RDX = 2,
+    TREG_RSP = 4,
     TREG_RSI = 6,
     TREG_RDI = 7,
 
@@ -74,7 +75,7 @@ enum {
     TREG_XMM6 = 22,
     TREG_XMM7 = 23,
 
-    TREG_ST0 = 4, // SP slot won't be used
+    TREG_ST0 = 24,
 
     TREG_MEM = 0x20,
 };
@@ -125,7 +126,7 @@ ST_DATA const int reg_classes[NB_REGS] = {
     /* ecx */ RC_INT | RC_RCX,
     /* edx */ RC_INT | RC_RDX,
     0,
-    /* st0 */ RC_ST0,
+    0,
     0,
     0,
     0,
@@ -147,7 +148,8 @@ ST_DATA const int reg_classes[NB_REGS] = {
        but they are not tagged with RC_FLOAT because they are
        callee saved on Windows */
     RC_XMM6,
-    RC_XMM7 
+    RC_XMM7,
+    /* st0 */ RC_ST0
 };
 
 static unsigned long func_sub_sp_offset;
@@ -2080,6 +2082,43 @@ void ggoto(void)
     vtop--;
 }
 
+/* Save the stack pointer onto the stack and return the location of its address */
+ST_FUNC void gen_vla_sp_save(int addr) {
+    /* mov %rsp,addr(%rbp)*/
+    gen_modrm64(0x89, TREG_RSP, VT_LOCAL, NULL, addr);
+}
+
+/* Restore the SP from a location on the stack */
+ST_FUNC void gen_vla_sp_restore(int addr) {
+    gen_modrm64(0x8b, TREG_RSP, VT_LOCAL, NULL, addr);
+}
+
+/* Subtract from the stack pointer, and push the resulting value onto the stack */
+ST_FUNC void gen_vla_alloc(CType *type, int align) {
+#ifdef TCC_TARGET_PE
+    /* alloca does more than just adjust %rsp on Windows */
+    vpush_global_sym(&func_old_type, TOK_alloca);
+    vswap(); /* Move alloca ref past allocation size */
+    gfunc_call(1);
+    vset(type, REG_IRET, 0);
+#else
+    int r;
+    r = gv(RC_INT); /* allocation size */
+    /* sub r,%rsp */
+    o(0x2b48);
+    o(0xe0 | REG_VALUE(r));
+    /* We align to 16 bytes rather than align */
+    /* and ~15, %rsp */
+    o(0xf0e48348);
+    /* mov %rsp, r */
+    o(0x8948);
+    o(0xe0 | REG_VALUE(r));
+    vpop();
+    vset(type, r, 0);
+#endif
+}
+
+
 /* end of x86-64 code generator */
 /*************************************************************/
 #endif /* ! TARGET_DEFS_ONLY */