Eliminate many SCNO_IS_VALID checks

By adding tcp->s_ent pointer tot syscall table entry,
we can replace sysent[tcp->scno] references by tcp->s_ent.
More importantly, we may ensure that tcp->s_ent is always valid,
regardless of tcp->scno value. This allows us to drop
SCNO_IS_VALID(tcp->scno) checks before we access syscall
table entry.

We can optimize (qual_flags[tcp->scno] & QUAL_foo) checks
with a similar technique.

Resulting code shrink:
   text    data     bss     dec     hex filename
 245975     700   19072  265747   40e13 strace.t3/strace
 245703     700   19072  265475   40d03 strace.t4/strace

* count.c (count_syscall): Use cheaper SCNO_IN_RANGE() check.
* defs.h: Add "int qual_flg" and "const struct sysent *s_ent"
to struct tcb. Remove "int u_nargs" from it.
Add UNDEFINED_SCNO constant which will mark undefined scnos
in tcp->qual_flg.
* pathtrace.c (pathtrace_match): Drop SCNO_IS_VALID check.
Use tcp->s_ent instead of sysent[tcp->scno].
* process.c (sys_prctl): Use tcp->s_ent->nargs instead of tcp->u_nargs.
(sys_waitid): Likewise.
* strace.c (init): Add compile-time check that DEFAULT_QUAL_FLAGS
constant is consistent with init code.
* syscall.c (decode_socket_subcall): Use tcp->s_ent->nargs
instead of tcp->u_nargs. Set tcp->qual_flg and tcp->s_ent.
(decode_ipc_subcall): Likewise.
(printargs): Use tcp->s_ent->nargs instead of tcp->u_nargs.
(printargs_lu): Likewise.
(printargs_ld): Likewise.
(get_scno): [MIPS,ALPHA] Use cheaper SCNO_IN_RANGE() check.
If !SCNO_IS_VALID, set tcp->s_ent and tcp->qual_flg to default values.
(internal_fork): Use tcp->s_ent instead of sysent[tcp->scno].
(syscall_fixup_for_fork_exec): Remove SCNO_IS_VALID check.
Use tcp->s_ent instead of sysent[tcp->scno].
(get_syscall_args): Likewise.
(get_error): Drop SCNO_IS_VALID check where it is redundant.
(dumpio): Drop SCNO_IS_VALID check where it is redundant.
Use tcp->s_ent instead of sysent[tcp->scno].
(trace_syscall_entering): Use (tcp->qual_flg & UNDEFINED_SCNO) instead
of SCNO_IS_VALID check. Use tcp->s_ent instead of sysent[tcp->scno].
Drop SCNO_IS_VALID check where it is redundant.
Print undefined syscall name with undefined_scno_name(tcp).
(trace_syscall_exiting): Likewise.
* util.c (setbpt): Use tcp->s_ent instead of sysent[tcp->scno].

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/count.c b/count.c
index 9405fd1..6c9b1c2 100644
--- a/count.c
+++ b/count.c
@@ -48,7 +48,9 @@
 void
 count_syscall(struct tcb *tcp, struct timeval *tv)
 {
-	if (!SCNO_IS_VALID(tcp->scno))
+	unsigned long scno = tcp->scno;
+
+	if (!SCNO_IN_RANGE(scno))
 		return;
 
 	if (!counts) {
@@ -57,9 +59,9 @@
 			die_out_of_memory();
 	}
 
-	counts[tcp->scno].calls++;
+	counts[scno].calls++;
 	if (tcp->u_error)
-		counts[tcp->scno].errors++;
+		counts[scno].errors++;
 
 	tv_sub(tv, tv, &tcp->etime);
 	if (tv_cmp(tv, &tcp->dtime) > 0) {
@@ -87,7 +89,7 @@
 	}
 	if (tv_cmp(tv, &shortest) < 0)
 		shortest = *tv;
-	tv_add(&counts[tcp->scno].time, &counts[tcp->scno].time, tv);
+	tv_add(&counts[scno].time, &counts[scno].time, tv);
 }
 
 static int
diff --git a/defs.h b/defs.h
index fc8f8e7..855a312 100644
--- a/defs.h
+++ b/defs.h
@@ -366,11 +366,24 @@
 extern struct pt_regs tile_regs;
 #endif
 
+struct sysent {
+	unsigned nargs;
+	int	sys_flags;
+	int	(*sys_func)();
+	const char *sys_name;
+};
+
+struct ioctlent {
+	const char *doth;
+	const char *symbol;
+	unsigned long code;
+};
+
 /* Trace Control Block */
 struct tcb {
 	int flags;		/* See below for TCB_ values */
 	int pid;		/* Process Id of this entry */
-	int u_nargs;		/* System call argument count */
+	int qual_flg;		/* qual_flags[scno] or DEFAULT_QUAL_FLAGS + RAW */
 	int u_error;		/* Error code */
 	long scno;		/* System call number */
 	long u_arg[MAX_ARGS];	/* System call arguments */
@@ -385,6 +398,7 @@
 	int curcol;		/* Output column for this process */
 	FILE *outf;		/* Output file for this process */
 	const char *auxstr;	/* Auxiliary info from syscall (see RVAL_STR) */
+	const struct sysent *s_ent; /* sysent[scno] or dummy struct for bad scno */
 	struct timeval stime;	/* System time usage as of last process wait */
 	struct timeval dtime;	/* Delta for system time usage */
 	struct timeval etime;	/* Syscall entry time */
@@ -447,14 +461,17 @@
 #endif
 
 /* qualifier flags */
-#define QUAL_TRACE	0001	/* this system call should be traced */
-#define QUAL_ABBREV	0002	/* abbreviate the structures of this syscall */
-#define QUAL_VERBOSE	0004	/* decode the structures of this syscall */
-#define QUAL_RAW	0010	/* print all args in hex for this syscall */
-#define QUAL_SIGNAL	0020	/* report events with this signal */
-#define QUAL_FAULT	0040	/* report events with this fault */
-#define QUAL_READ	0100	/* dump data read on this file descriptor */
-#define QUAL_WRITE	0200	/* dump data written to this file descriptor */
+#define QUAL_TRACE	0x001	/* this system call should be traced */
+#define QUAL_ABBREV	0x002	/* abbreviate the structures of this syscall */
+#define QUAL_VERBOSE	0x004	/* decode the structures of this syscall */
+#define QUAL_RAW	0x008	/* print all args in hex for this syscall */
+#define QUAL_SIGNAL	0x010	/* report events with this signal */
+#define QUAL_FAULT	0x020	/* report events with this fault */
+#define QUAL_READ	0x040	/* dump data read on this file descriptor */
+#define QUAL_WRITE	0x080	/* dump data written to this file descriptor */
+#define UNDEFINED_SCNO	0x100	/* Used only in tcp->qual_flg */
+
+#define DEFAULT_QUAL_FLAGS (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
 
 #define entering(tcp)	(!((tcp)->flags & TCB_INSYSCALL))
 #define exiting(tcp)	((tcp)->flags & TCB_INSYSCALL)
@@ -728,19 +745,6 @@
 # define widen_to_long(v) ((long)(v))
 #endif
 
-struct sysent {
-	unsigned nargs;
-	int	sys_flags;
-	int	(*sys_func)();
-	const char *sys_name;
-};
-
-struct ioctlent {
-	const char *doth;
-	const char *symbol;
-	unsigned long code;
-};
-
 extern const struct sysent *sysent;
 extern unsigned nsyscalls;
 extern const char *const *errnoent;
diff --git a/pathtrace.c b/pathtrace.c
index 33c9b75..7f90dd5 100644
--- a/pathtrace.c
+++ b/pathtrace.c
@@ -162,10 +162,7 @@
 	if (selected[0] == NULL)
 		return 1;
 
-	if (!SCNO_IS_VALID(tcp->scno))
-		return 0;
-
-	s = &sysent[tcp->scno];
+	s = tcp->s_ent;
 
 	if (!(s->sys_flags & (TRACE_FILE | TRACE_DESC)))
 		return 0;
diff --git a/process.c b/process.c
index 0b702e7..a1f963e 100644
--- a/process.c
+++ b/process.c
@@ -302,7 +302,7 @@
 			break;
 #endif
 		default:
-			for (i = 1; i < tcp->u_nargs; i++)
+			for (i = 1; i < tcp->s_ent->nargs; i++)
 				tprintf(", %#lx", tcp->u_arg[i]);
 			break;
 		}
@@ -1137,7 +1137,7 @@
 		/* options */
 		tprints(", ");
 		printflags(wait4_options, tcp->u_arg[3], "W???");
-		if (tcp->u_nargs > 4) {
+		if (tcp->s_ent->nargs > 4) {
 			/* usage */
 			tprints(", ");
 			if (!tcp->u_arg[4])
diff --git a/strace.c b/strace.c
index 1052a88..8d7ee74 100644
--- a/strace.c
+++ b/strace.c
@@ -1543,6 +1543,9 @@
 	qualify("trace=all");
 	qualify("abbrev=all");
 	qualify("verbose=all");
+#if DEFAULT_QUAL_FLAGS != (QUAL_TRACE | QUAL_ABBREV | QUAL_VERBOSE)
+# error Bug in DEFAULT_QUAL_FLAGS
+#endif
 	qualify("signal=all");
 	while ((c = getopt(argc, argv,
 		"+bcCdfFhiqrtTvVxyz"
diff --git a/syscall.c b/syscall.c
index b1a407b..fa06156 100644
--- a/syscall.c
+++ b/syscall.c
@@ -556,16 +556,18 @@
 decode_socket_subcall(struct tcb *tcp)
 {
 	unsigned long addr;
-	unsigned int i, size;
+	unsigned int i, n, size;
 
 	if (tcp->u_arg[0] < 0 || tcp->u_arg[0] >= SYS_socket_nsubcalls)
 		return;
 
 	tcp->scno = SYS_socket_subcall + tcp->u_arg[0];
+	tcp->qual_flg = qual_flags[tcp->scno];
+	tcp->s_ent = &sysent[tcp->scno];
 	addr = tcp->u_arg[1];
-	tcp->u_nargs = sysent[tcp->scno].nargs;
 	size = current_wordsize;
-	for (i = 0; i < tcp->u_nargs; ++i) {
+	n = tcp->s_ent->nargs;
+	for (i = 0; i < n; ++i) {
 		if (size == sizeof(int)) {
 			unsigned int arg;
 			if (umove(tcp, addr, &arg) < 0)
@@ -587,14 +589,16 @@
 static void
 decode_ipc_subcall(struct tcb *tcp)
 {
-	unsigned int i;
+	unsigned int i, n;
 
 	if (tcp->u_arg[0] < 0 || tcp->u_arg[0] >= SYS_ipc_nsubcalls)
 		return;
 
 	tcp->scno = SYS_ipc_subcall + tcp->u_arg[0];
-	tcp->u_nargs = sysent[tcp->scno].nargs;
-	for (i = 0; i < tcp->u_nargs; i++)
+	tcp->qual_flg = qual_flags[tcp->scno];
+	tcp->s_ent = &sysent[tcp->scno];
+	n = tcp->s_ent->nargs;
+	for (i = 0; i < n; i++)
 		tcp->u_arg[i] = tcp->u_arg[i + 1];
 }
 #endif
@@ -604,8 +608,8 @@
 {
 	if (entering(tcp)) {
 		int i;
-
-		for (i = 0; i < tcp->u_nargs; i++)
+		int n = tcp->s_ent->nargs;
+		for (i = 0; i < n; i++)
 			tprintf("%s%#lx", i ? ", " : "", tcp->u_arg[i]);
 	}
 	return 0;
@@ -616,8 +620,8 @@
 {
 	if (entering(tcp)) {
 		int i;
-
-		for (i = 0; i < tcp->u_nargs; i++)
+		int n = tcp->s_ent->nargs;
+		for (i = 0; i < n; i++)
 			tprintf("%s%lu", i ? ", " : "", tcp->u_arg[i]);
 	}
 	return 0;
@@ -628,8 +632,8 @@
 {
 	if (entering(tcp)) {
 		int i;
-
-		for (i = 0; i < tcp->u_nargs; i++)
+		int n = tcp->s_ent->nargs;
+		for (i = 0; i < n; i++)
 			tprintf("%s%ld", i ? ", " : "", tcp->u_arg[i]);
 	}
 	return 0;
@@ -1305,7 +1309,7 @@
 	mips_r2 = regs[REG_V0];
 
 	scno = mips_r2;
-	if (!SCNO_IS_VALID(scno)) {
+	if (!SCNO_IN_RANGE(scno)) {
 		if (mips_a3 == 0 || mips_a3 == -1) {
 			if (debug_flag)
 				fprintf(stderr, "stray syscall exit: v0 = %ld\n", scno);
@@ -1318,7 +1322,7 @@
 	if (upeek(tcp, REG_V0, &scno) < 0)
 		return -1;
 
-	if (!SCNO_IS_VALID(scno)) {
+	if (!SCNO_IN_RANGE(scno)) {
 		if (mips_a3 == 0 || mips_a3 == -1) {
 			if (debug_flag)
 				fprintf(stderr, "stray syscall exit: v0 = %ld\n", scno);
@@ -1335,7 +1339,7 @@
 	 * Do some sanity checks to figure out if it's
 	 * really a syscall entry
 	 */
-	if (!SCNO_IS_VALID(scno)) {
+	if (!SCNO_IN_RANGE(scno)) {
 		if (alpha_a3 == 0 || alpha_a3 == -1) {
 			if (debug_flag)
 				fprintf(stderr, "stray syscall exit: r0 = %ld\n", scno);
@@ -1453,6 +1457,19 @@
 #endif
 
 	tcp->scno = scno;
+	if (SCNO_IS_VALID(tcp->scno)) {
+		tcp->s_ent = &sysent[scno];
+		tcp->qual_flg = qual_flags[scno];
+	} else {
+		static const struct sysent unknown = {
+			.nargs = MAX_ARGS,
+			.sys_flags = 0,
+			.sys_func = printargs,
+			.sys_name = "unknown", /* not used */
+		};
+		tcp->s_ent = &unknown;
+		tcp->qual_flg = UNDEFINED_SCNO | QUAL_RAW | DEFAULT_QUAL_FLAGS;
+	}
 	return 1;
 }
 
@@ -1567,8 +1584,9 @@
 		 * CLONE_UNTRACED, so we keep the same logic with that option
 		 * and don't trace it.
 		 */
-		if ((sysent[tcp->scno].sys_func == sys_clone) &&
-		    (tcp->u_arg[ARG_FLAGS] & CLONE_UNTRACED))
+		if ((tcp->s_ent->sys_func == sys_clone)
+		 && (tcp->u_arg[ARG_FLAGS] & CLONE_UNTRACED)
+		)
 			return;
 		setbpt(tcp);
 	} else {
@@ -1603,10 +1621,7 @@
 	 */
 	int (*func)();
 
-	if (!SCNO_IS_VALID(tcp->scno))
-		return;
-
-	func = sysent[tcp->scno].sys_func;
+	func = tcp->s_ent->sys_func;
 
 	if (   sys_fork == func
 	    || sys_vfork == func
@@ -1634,10 +1649,7 @@
 {
 	int i, nargs;
 
-	if (SCNO_IS_VALID(tcp->scno))
-		nargs = tcp->u_nargs = sysent[tcp->scno].nargs;
-	else
-		nargs = tcp->u_nargs = MAX_ARGS;
+	nargs = tcp->s_ent->nargs;
 
 #if defined(S390) || defined(S390X)
 	for (i = 0; i < nargs; ++i)
@@ -1874,10 +1886,10 @@
 		printleader(tcp);
 		if (scno_good != 1)
 			tprints("????" /* anti-trigraph gap */ "(");
-		else if (!SCNO_IS_VALID(tcp->scno))
+		else if (tcp->qual_flg & UNDEFINED_SCNO)
 			tprintf("%s(", undefined_scno_name(tcp));
 		else
-			tprintf("%s(", sysent[tcp->scno].sys_name);
+			tprintf("%s(", tcp->s_ent->sys_name);
 		/*
 		 * " <unavailable>" will be added later by the code which
 		 * detects ptrace errors.
@@ -1886,29 +1898,29 @@
 	}
 
 #if defined(SYS_socket_subcall) || defined(SYS_ipc_subcall)
-	while (SCNO_IS_VALID(tcp->scno)) {
+	while (1) {
 # ifdef SYS_socket_subcall
-		if (sysent[tcp->scno].sys_func == sys_socketcall) {
+		if (tcp->s_ent->sys_func == sys_socketcall) {
 			decode_socket_subcall(tcp);
 			break;
 		}
 # endif
 # ifdef SYS_ipc_subcall
-		if (sysent[tcp->scno].sys_func == sys_ipc) {
+		if (tcp->s_ent->sys_func == sys_ipc) {
 			decode_ipc_subcall(tcp);
 			break;
 		}
 # endif
 		break;
 	}
-#endif /* SYS_socket_subcall || SYS_ipc_subcall */
+#endif
 
 	if (need_fork_exec_workarounds)
 		syscall_fixup_for_fork_exec(tcp);
 
-	if ((SCNO_IS_VALID(tcp->scno) &&
-	     !(qual_flags[tcp->scno] & QUAL_TRACE)) ||
-	    (tracing_paths && !pathtrace_match(tcp))) {
+	if (!(tcp->qual_flg & QUAL_TRACE)
+	 || (tracing_paths && !pathtrace_match(tcp))
+	) {
 		tcp->flags |= TCB_INSYSCALL | TCB_FILTERED;
 		return 0;
 	}
@@ -1921,16 +1933,14 @@
 	}
 
 	printleader(tcp);
-	if (!SCNO_IS_VALID(tcp->scno))
+	if (tcp->qual_flg & UNDEFINED_SCNO)
 		tprintf("%s(", undefined_scno_name(tcp));
 	else
-		tprintf("%s(", sysent[tcp->scno].sys_name);
-	if (!SCNO_IS_VALID(tcp->scno) ||
-	    ((qual_flags[tcp->scno] & QUAL_RAW) &&
-	     sysent[tcp->scno].sys_func != sys_exit))
+		tprintf("%s(", tcp->s_ent->sys_name);
+	if ((tcp->qual_flg & QUAL_RAW) && tcp->s_ent->sys_func != sys_exit)
 		res = printargs(tcp);
 	else
-		res = (*sysent[tcp->scno].sys_func)(tcp);
+		res = tcp->s_ent->sys_func(tcp);
 
 	fflush(tcp->outf);
  ret:
@@ -2102,9 +2112,7 @@
 {
 	int u_error = 0;
 	int check_errno = 1;
-	if (SCNO_IN_RANGE(tcp->scno)
-	 && (sysent[tcp->scno].sys_flags & SYSCALL_NEVER_FAILS)
-	) {
+	if (tcp->s_ent->sys_flags & SYSCALL_NEVER_FAILS) {
 		check_errno = 0;
 	}
 #if defined(S390) || defined(S390X)
@@ -2333,31 +2341,32 @@
 static void
 dumpio(struct tcb *tcp)
 {
+	int (*func)();
+
 	if (syserror(tcp))
 		return;
-	if (tcp->u_arg[0] < 0 || tcp->u_arg[0] >= MAX_QUALS)
+	if ((unsigned long) tcp->u_arg[0] >= MAX_QUALS)
 		return;
-	if (!SCNO_IS_VALID(tcp->scno))
-		return;
-	if (sysent[tcp->scno].sys_func == printargs)
+	func = tcp->s_ent->sys_func;
+	if (func == printargs)
 		return;
 	if (qual_flags[tcp->u_arg[0]] & QUAL_READ) {
-		if (sysent[tcp->scno].sys_func == sys_read ||
-		    sysent[tcp->scno].sys_func == sys_pread ||
-		    sysent[tcp->scno].sys_func == sys_recv ||
-		    sysent[tcp->scno].sys_func == sys_recvfrom)
+		if (func == sys_read ||
+		    func == sys_pread ||
+		    func == sys_recv ||
+		    func == sys_recvfrom)
 			dumpstr(tcp, tcp->u_arg[1], tcp->u_rval);
-		else if (sysent[tcp->scno].sys_func == sys_readv)
+		else if (func == sys_readv)
 			dumpiov(tcp, tcp->u_arg[2], tcp->u_arg[1]);
 		return;
 	}
 	if (qual_flags[tcp->u_arg[0]] & QUAL_WRITE) {
-		if (sysent[tcp->scno].sys_func == sys_write ||
-		    sysent[tcp->scno].sys_func == sys_pwrite ||
-		    sysent[tcp->scno].sys_func == sys_send ||
-		    sysent[tcp->scno].sys_func == sys_sendto)
+		if (func == sys_write ||
+		    func == sys_pwrite ||
+		    func == sys_send ||
+		    func == sys_sendto)
 			dumpstr(tcp, tcp->u_arg[1], tcp->u_arg[2]);
-		else if (sysent[tcp->scno].sys_func == sys_writev)
+		else if (func == sys_writev)
 			dumpiov(tcp, tcp->u_arg[2], tcp->u_arg[1]);
 		return;
 	}
@@ -2408,10 +2417,10 @@
 	if ((followfork < 2 && printing_tcp != tcp) || (tcp->flags & TCB_REPRINT)) {
 		tcp->flags &= ~TCB_REPRINT;
 		printleader(tcp);
-		if (!SCNO_IS_VALID(tcp->scno))
+		if (tcp->qual_flg & UNDEFINED_SCNO)
 			tprintf("<... %s resumed> ", undefined_scno_name(tcp));
 		else
-			tprintf("<... %s resumed> ", sysent[tcp->scno].sys_name);
+			tprintf("<... %s resumed> ", tcp->s_ent->sys_name);
 	}
 	printing_tcp = tcp;
 
@@ -2426,8 +2435,7 @@
 	}
 
 	sys_res = 0;
-	if (!SCNO_IS_VALID(tcp->scno)
-	    || (qual_flags[tcp->scno] & QUAL_RAW)) {
+	if (tcp->qual_flg & QUAL_RAW) {
 		/* sys_res = printargs(tcp); - but it's nop on sysexit */
 	} else {
 	/* FIXME: not_failing_only (IOW, option -z) is broken:
@@ -2440,14 +2448,13 @@
 	 */
 		if (not_failing_only && tcp->u_error)
 			goto ret;	/* ignore failed syscalls */
-		sys_res = (*sysent[tcp->scno].sys_func)(tcp);
+		sys_res = tcp->s_ent->sys_func(tcp);
 	}
 
 	tprints(") ");
 	tabto();
 	u_error = tcp->u_error;
-	if (!SCNO_IS_VALID(tcp->scno) ||
-	    qual_flags[tcp->scno] & QUAL_RAW) {
+	if (tcp->qual_flg & QUAL_RAW) {
 		if (u_error)
 			tprintf("= -1 (errno %ld)", u_error);
 		else
diff --git a/util.c b/util.c
index f0d024d..4c22c5f 100644
--- a/util.c
+++ b/util.c
@@ -1332,8 +1332,8 @@
 			}
 	}
 
-	if (sysent[tcp->scno].sys_func == sys_fork ||
-	    sysent[tcp->scno].sys_func == sys_vfork) {
+	if (tcp->s_ent->sys_func == sys_fork ||
+	    tcp->s_ent->sys_func == sys_vfork) {
 		if (arg_setup(tcp, &state) < 0
 		    || get_arg0(tcp, &state, &tcp->inst[0]) < 0
 		    || get_arg1(tcp, &state, &tcp->inst[1]) < 0
@@ -1349,7 +1349,7 @@
 		return 0;
 	}
 
-	if (sysent[tcp->scno].sys_func == sys_clone) {
+	if (tcp->s_ent->sys_func == sys_clone) {
 		/* ia64 calls directly `clone (CLONE_VFORK | CLONE_VM)'
 		   contrary to x86 vfork above.  Even on x86 we turn the
 		   vfork semantics into plain fork - each application must not