diff --git a/gcc/calls.cc b/gcc/calls.cc
index 78ead6fd4ed..d0f6f2bdfd6 100644
--- a/gcc/calls.cc
+++ b/gcc/calls.cc
@@ -1280,6 +1280,33 @@ maybe_complain_about_tail_call (tree call_expr, const char *reason)
   CALL_EXPR_MUST_TAIL_CALL (call_expr) = 0;
 }
 
+/* Return the original integer type if ARG is copied from the incoming
+   argument.  Otherwise return integer_type_node.  */
+
+static tree
+get_small_int_arg_type (tree arg)
+{
+  if (TREE_CODE (arg) != SSA_NAME)
+    return integer_type_node;
+
+  if (!SSA_NAME_IS_DEFAULT_DEF (arg))
+    return integer_type_node;
+
+  tree var = SSA_NAME_VAR (arg);
+  if (TREE_CODE (var) != PARM_DECL)
+    return integer_type_node;
+  if (TYPE_MODE (TREE_TYPE (arg)) != TYPE_MODE (DECL_ARG_TYPE (var)))
+    return integer_type_node;
+
+  /* Use the incoming small integer argument type for the outgoing
+     argument.  If callee is a global function, we always properly
+     extend the incoming small integer arguments in callee.  If
+     callee is a local function, since DECL_ARG_TYPE has the original
+     small integer type, we will extend the incoming small integer
+     arguments in callee if needed.  */
+  return TREE_TYPE (arg);
+}
+
 /* Fill in ARGS_SIZE and ARGS array based on the parameters found in
    CALL_EXPR EXP.
 
@@ -1391,7 +1418,7 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED,
       else if (promote_p
 	       && INTEGRAL_TYPE_P (type)
 	       && TYPE_PRECISION (type) < TYPE_PRECISION (integer_type_node))
-	type = integer_type_node;
+	type = get_small_int_arg_type (args[i].tree_value);
 
       /* If TYPE is a transparent union or record, pass things the way
 	 we would pass the first field of the union or record.  We have
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-1.c b/gcc/testsuite/gcc.target/i386/pr14907-1.c
new file mode 100644
index 00000000000..231819ed675
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-1.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*foo:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	baz
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern int baz (char);
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-10.c b/gcc/testsuite/gcc.target/i386/pr14907-10.c
new file mode 100644
index 00000000000..099c4dc81d1
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-10.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "ia32*" "" "" { target { { *-*-linux* *-*-gnu* } && ia32 } } {^\t?\.} } } */
+
+/*
+ia32*foo:
+ia32*.LFB0:
+ia32*	.cfi_startproc
+ia32*	movsbl	4\(%esp\), %eax
+ia32*	movl	%eax, 4\(%esp\)
+ia32*	jmp	baz
+ia32*	.cfi_endproc
+ia32*...
+*/
+
+extern int baz (short);
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-11.c b/gcc/testsuite/gcc.target/i386/pr14907-11.c
new file mode 100644
index 00000000000..12ac165c298
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-11.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern int baz (char, char);
+
+int
+foo (char c1, char c2)
+{
+  return baz (c1, c2) + 1;
+}
+
+/* { dg-final { scan-assembler-not "movsbl" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-12.c b/gcc/testsuite/gcc.target/i386/pr14907-12.c
new file mode 100644
index 00000000000..6cda72ef3a2
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-12.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+struct s
+{
+  char c[20];
+};
+
+extern struct s baz (char, char);
+
+struct s
+foo (char c1, char c2)
+{
+  return baz (c1, c2);
+}
+
+/* { dg-final { scan-assembler-not "movsbl" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-13.c b/gcc/testsuite/gcc.target/i386/pr14907-13.c
new file mode 100644
index 00000000000..b4130fdcb57
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-13.c
@@ -0,0 +1,12 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+extern int baz (char, char, ...);
+
+int
+foo (char c1, char c2)
+{
+  return baz (c1, c2, 0, 0, 0, 1);
+}
+
+/* { dg-final { scan-assembler-not "movsbl" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-14.c b/gcc/testsuite/gcc.target/i386/pr14907-14.c
new file mode 100644
index 00000000000..9b8d7a7607d
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-14.c
@@ -0,0 +1,17 @@
+/* { dg-do compile } */
+/* { dg-options "-O2" } */
+
+struct s
+{
+  char c[20];
+};
+
+extern struct s baz (char, char, ...);
+
+struct s
+foo (char c1, char c2)
+{
+  return baz (c1, c2, 0, 1);
+}
+
+/* { dg-final { scan-assembler-not "movsbl" } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-15.c b/gcc/testsuite/gcc.target/i386/pr14907-15.c
new file mode 100644
index 00000000000..08bc4ea9ac8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-15.c
@@ -0,0 +1,26 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x64*" "" "" { target { { *-*-linux* *-*-gnu* } && { ! ia32 } } } {^\t?\.} } } */
+
+/*
+x64*foo:
+x64*.LFB1:
+x64*	.cfi_startproc
+x64*	jmp	baz
+x64*	.cfi_endproc
+x64*...
+*/
+
+ __attribute__ ((noinline))
+static int
+baz (char c1)
+{
+  return c1;
+}
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-16.c b/gcc/testsuite/gcc.target/i386/pr14907-16.c
new file mode 100644
index 00000000000..48c255ffb20
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-16.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x64*" "" "" { target { { *-*-linux* *-*-gnu* } && { ! ia32 } } } {^\t?\.} } } */
+
+/*
+x64*foo:
+x64*.LFB0:
+x64*	.cfi_startproc
+x64*	andl	\$1, %edi
+x64*	jmp	baz
+x64*	.cfi_endproc
+x64*...
+*/
+
+#include <stdbool.h>
+
+extern int baz (bool);
+
+int
+foo (int c1)
+{
+  return baz (c1 & 0x1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-2.c b/gcc/testsuite/gcc.target/i386/pr14907-2.c
new file mode 100644
index 00000000000..5da7b029279
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-2.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*foo:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	baz
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern int baz (int, int, int, int, int, int, char, char);
+
+int
+foo (int a1, int a2, int a3, int a4, int a5, int a6, char c1, char c2)
+{
+  return baz (a1, a2, a3, a4, a5, a6, c1, c2);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-3.c b/gcc/testsuite/gcc.target/i386/pr14907-3.c
new file mode 100644
index 00000000000..a8fb13f28f8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-3.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*c1:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	c2
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern char c2 (char);
+
+char
+c1 (char c)
+{
+  return c2 (c);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-4.c b/gcc/testsuite/gcc.target/i386/pr14907-4.c
new file mode 100644
index 00000000000..b5fb92fefcc
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-4.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*foo:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	baz
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern int baz (short);
+
+int
+foo (short c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-5.c b/gcc/testsuite/gcc.target/i386/pr14907-5.c
new file mode 100644
index 00000000000..d9abb5c8cfb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-5.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*foo:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	baz
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern int baz (int, int, int, int, int, int, short, short);
+
+int
+foo (int a1, int a2, int a3, int a4, int a5, int a6, short c1, short c2)
+{
+  return baz (a1, a2, a3, a4, a5, a6, c1, c2);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-6.c b/gcc/testsuite/gcc.target/i386/pr14907-6.c
new file mode 100644
index 00000000000..b6d0183656a
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-6.c
@@ -0,0 +1,21 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x86*" "" "" { target *-*-linux* *-*-gnu* } {^\t?\.} } } */
+
+/*
+x86*c1:
+x86*.LFB0:
+x86*	.cfi_startproc
+x86*	jmp	c2
+x86*	.cfi_endproc
+x86*...
+*/
+
+extern short c2 (short);
+
+short
+c1 (short c)
+{
+  return c2 (c);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-7.c b/gcc/testsuite/gcc.target/i386/pr14907-7.c
new file mode 100644
index 00000000000..fbf511f691e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-7.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x64*" "" "" { target { { *-*-linux* *-*-gnu* } && { ! ia32 } } } {^\t?\.} } } */
+
+/*
+x64*foo:
+x64*.LFB0:
+x64*	.cfi_startproc
+x64*	movsbl	%dil, %edi
+x64*	jmp	baz
+x64*	.cfi_endproc
+x64*...
+*/
+
+extern int baz (int);
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-8.c b/gcc/testsuite/gcc.target/i386/pr14907-8.c
new file mode 100644
index 00000000000..7d2611398c0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-8.c
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "ia32*" "" "" { target { { *-*-linux* *-*-gnu* } && ia32 } } {^\t?\.} } } */
+
+/*
+ia32*foo:
+ia32*.LFB0:
+ia32*	.cfi_startproc
+ia32*	movsbl	4\(%esp\), %eax
+ia32*	movl	%eax, 4\(%esp\)
+ia32*	jmp	baz
+ia32*	.cfi_endproc
+ia32*...
+*/
+
+extern int baz (int);
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
diff --git a/gcc/testsuite/gcc.target/i386/pr14907-9.c b/gcc/testsuite/gcc.target/i386/pr14907-9.c
new file mode 100644
index 00000000000..a22383694bf
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr14907-9.c
@@ -0,0 +1,22 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -g0" } */
+/* Keep labels and directives ('.cfi_startproc', '.cfi_endproc').  */
+/* { dg-final { check-function-bodies "x64*" "" "" { target { { *-*-linux* *-*-gnu* } && { ! ia32 } } } {^\t?\.} } } */
+
+/*
+x64*foo:
+x64*.LFB0:
+x64*	.cfi_startproc
+x64*	movsbl	%dil, %edi
+x64*	jmp	baz
+x64*	.cfi_endproc
+x64*...
+*/
+
+extern int baz (short);
+
+int
+foo (char c1)
+{
+  return baz (c1);
+}
