# Assignment 6: Low-level refactoring and performance Code
Benefits of refactoring:
- Readability and modularity
- Easier to debug
- Reduces compilation time (parallel compilation of multiple files)
### randall.c edited
```c=
#include <errno.h>
#include <stdlib.h>
#include "options.h"
#include "output.h"
#include "rand64-hw.h"
#include "rand64-sw.h"
#include "my-lrand48-r.h"
/* Main program, which outputs N bytes of random data. */
int main (int argc, char **argv) {
long long nbytes = 0;
int inputSize = 100;
int outputSize = 100;
char* input = malloc(inputSize * sizeof(char));
char* output = malloc(outputSize * sizeof(char));
bool stopped = false;
options(argc, argv, &nbytes, input, output, &stopped);
if (stopped) {
free(input);
free(output);
return 1;
}
// Use nbytes, input, and output as needed
//printf("nbytes: %lld\n", nbytes);
//printf("input: %s\n", input);
//printf("output: %s\n", output);
if (strcmp(input, "rdrand") == 0 && !rdrand_supported ()) { //check for working hardware
fprintf (stderr, "hardward random-number generation is not available!\n");
free(input);
free(output);
return 1;
}
/* If there's no work to do, don't worry about which library to use. */
if (nbytes == 0) {
free(input);
free(output);
return 0;
}
/* Now that we know we have work to do, arrange to use the
appropriate library. */
void (*initialize) (void);
unsigned long long (*rand64) (void);
void (*finalize) (void);
if (rdrand_supported ())
{
initialize = hardware_rand64_init;
rand64 = hardware_rand64;
finalize = hardware_rand64_fini;
}
else if (strcmp(input, "lrand48_r"))
{
initialize = lrand48_r_init;
rand64 = my_lrand48_r;
finalize = lrand48_r_fini;
}
else
{
if (input[0] == '/')
setPath(input); //change path
initialize = software_rand64_init;
rand64 = software_rand64;
finalize = software_rand64_fini;
}
initialize ();
int wordsize = sizeof rand64 ();
int output_errno = 0;
do
{
unsigned long long x = rand64 ();
int outbytes = nbytes < wordsize ? nbytes : wordsize;
if (!writebytes (x, outbytes, output))
{
output_errno = errno;
break;
}
nbytes -= outbytes;
}
while (0 < nbytes);
if (fclose (stdout) != 0)
output_errno = errno;
if (output_errno)
{
errno = output_errno;
perror ("output");
}
finalize ();
free(input);
free(output);
return !!output_errno;
}
```
### options.h
```c=
#ifndef OPTIONS_H
#define OPTIONS_H
#include <stdbool.h>
bool isPosDec(char *arg);
void options(int argc, char **argv, long long *nbytes, char *input, char *output, bool *stopped);
#endif /* OPTIONS_H */
```
### options.c
```c=
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
bool isPosDec(char *num) {
while (*num) {
if (!isdigit(*num)) {
return false; // Non-digit character found
}
num++;
}
return true;
}
void options(int argc, char **argv, long long *nbytes, char *input, char *output, bool *stopped) {
int opt;
strcpy(input, "rdrand");
strcpy(output, "stdio");
bool inputErr = false;
bool outputErr = false;
bool bytesErr = false;
while ((opt = getopt(argc, argv, ":i:o:")) != -1) {
switch (opt) {
case 'i':
if (strcmp(optarg, "rdrand") == 0 || strcmp(optarg, "lrand48_r") == 0 || (optarg[0] == '/' && access(optarg, F_OK) != -1))
strcpy(input, optarg); //get arg input
else
inputErr = true;
break;
case 'o':
if (strcmp(optarg, "stdio") == 0 || isPosDec(optarg))
strcpy(output, optarg); //get arg output
else
outputErr = true;
break;
default:
fprintf (stderr, "Usage: %s [nBYTES] [-i input] [-o output]\n", argv[0]);
*stopped = true;
return;
}
if (inputErr) { //stop early if there is an error detected
fprintf (stderr, "%s: usage: %s -i INPUT=rdrand, lrand48, /F\n", argv[0], argv[0]);
*stopped = true;
return;
} else if (outputErr) {
fprintf (stderr, "%s: usage: %s -o OUTPUT=stdio, N (pos decimal integer)\n", argv[0], argv[0]);
*stopped = true;
return;
}
}
if (optind < argc) { //for no arg cases like NBYTES
char *endptr; //original case
errno = 0;
*nbytes = strtoll (argv[optind], &endptr, 10);
if (errno)
perror (argv[optind]);
else
bytesErr = !*endptr && 0 <= *nbytes;
}
if (!bytesErr) {
fprintf (stderr, "%s: usage: %s NBYTES\n", argv[0], argv[0]);
*stopped = true;
return;
}
}
```
### Makefile
```c=
# Optimization level. Change this -O2 to -Og or -O0 or whatever.
OPTIMIZE =
src-files = *.c
header-files = *.h
# The C compiler and its options.
CC = gcc
CFLAGS = $(OPTIMIZE) -g3 -Wall -Wextra -fanalyzer \
-march=native -mtune=native -mrdrnd
# The archiver command, its options and filename extension.
TAR = tar
TARFLAGS = --gzip --transform 's,^,randall/,'
TAREXT = tgz
default: randall
randall: $(src-files) $(header-files)
$(CC) $(CFLAGS) $(src-files) $(header-files) -o $(@)
assignment: randall-assignment.$(TAREXT)
assignment-files = COPYING Makefile randall.c
randall-assignment.$(TAREXT): $(assignment-files)
$(TAR) $(TARFLAGS) -cf $@ $(assignment-files)
submission-tarball: randall-submission.$(TAREXT)
submission-files = $(assignment-files) \
notes.txt *.c *.h rand.data
randall-submission.$(TAREXT): $(submission-files)
$(TAR) $(TARFLAGS) -cf $@ $(submission-files)
repository-tarball:
$(TAR) -czf randall-git.tgz .git
check: randall
$(make randall)
@if [ $$(./randall 26 | wc -c) -ne 26 ]; then \
echo "Test of length failed! Expected 26, got $$(./randall 26 | wc -c)"; \
elif [ $$(./randall -75 -e | wc -c) -ne 0 ]; then \
echo "Test of incorrect argument failed"; \
elif [ $$(./randall 50 -e | wc -c) -eq 50 ]; then \
echo "Test of incorrect argument failed"; \
elif [ $$(./randall 25 -i wrong_input | wc -c) -eq 25 ]; then \
echo "#1 Test of input argument failed"; \
elif [ $$(./randall -i rdrand 12| wc -c) -ne 12 ]; then \
echo "#2 Test of input argument failed"; \
elif [ $$(./randall -i lrand48_r 13| wc -c) -ne 13 ]; then \
echo "#3 Test of input argument failed"; \
elif [ $$(./randall -i /dev/random 14| wc -c) -ne 14 ]; then \
echo "#4 Test of input argument failed"; \
elif [ $$(./randall -i /dev/urandom 15| wc -c) -ne 15 ]; then \
echo "#5 Test of input argument failed"; \
elif [ $$(./randall -i /dev/jk 16| wc -c) -eq 16 ]; then \
echo "#6 Test of input argument failed"; \
elif [ $$(./randall 75 -o wrong_output | wc -c) -eq 75 ]; then \
echo "#1 Test of output argument failed"; \
elif [ $$(./randall 17 -o stdio | wc -c) -ne 17 ]; then \
echo "#2 Test of input argument failed"; \
elif [ $$(./randall 18 -o 13 | wc -c) -ne 18 ]; then \
echo "#3 Test of input argument failed"; \
elif [ $$(./randall 98 -o 13 -i /dev/urandom | wc -c) -ne 98 ]; then \
echo "full test failed!"; \
fi
.PHONY: default clean assignment submission-tarball repository-tarball
clean:
rm -f *.o *.$(TAREXT) randall
```
### randall.c original
```c=
#include <cpuid.h>
#include <errno.h>
#include <immintrin.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
/* Hardware implementation. */
/* Description of the current CPU. */
struct cpuid { unsigned eax, ebx, ecx, edx; };
/* Return information about the CPU. See <http://wiki.osdev.org/CPUID>. */
static struct cpuid
cpuid (unsigned int leaf, unsigned int subleaf)
{
struct cpuid result;
asm ("cpuid"
: "=a" (result.eax), "=b" (result.ebx),
"=c" (result.ecx), "=d" (result.edx)
: "a" (leaf), "c" (subleaf));
return result;
}
/* Return true if the CPU supports the RDRAND instruction. */
static _Bool
rdrand_supported (void)
{
struct cpuid extended = cpuid (1, 0);
return (extended.ecx & bit_RDRND) != 0;
}
/* Initialize the hardware rand64 implementation. */
static void
hardware_rand64_init (void)
{
}
/* Return a random value, using hardware operations. */
static unsigned long long
hardware_rand64 (void)
{
unsigned long long int x;
/* Work around GCC bug 107565
<https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107565>. */
x = 0;
while (! _rdrand64_step (&x))
continue;
return x;
}
/* Finalize the hardware rand64 implementation. */
static void
hardware_rand64_fini (void)
{
}
/* Software implementation. */
/* Input stream containing random bytes. */
static FILE *urandstream;
/* Initialize the software rand64 implementation. */
static void
software_rand64_init (void)
{
urandstream = fopen ("/dev/random", "r");
if (! urandstream)
abort ();
}
/* Return a random value, using software operations. */
static unsigned long long
software_rand64 (void)
{
unsigned long long int x;
if (fread (&x, sizeof x, 1, urandstream) != 1)
abort ();
return x;
}
/* Finalize the software rand64 implementation. */
static void
software_rand64_fini (void)
{
fclose (urandstream);
}
static bool
writebytes (unsigned long long x, int nbytes)
{
do
{
if (putchar (x) < 0)
return false;
x >>= CHAR_BIT;
nbytes--;
}
while (0 < nbytes);
return true;
}
/* Main program, which outputs N bytes of random data. */
int
main (int argc, char **argv)
{
/* Check arguments. */
bool valid = false;
long long nbytes;
if (argc == 2)
{
char *endptr;
errno = 0;
nbytes = strtoll (argv[1], &endptr, 10);
if (errno)
perror (argv[1]);
else
valid = !*endptr && 0 <= nbytes;
}
if (!valid)
{
fprintf (stderr, "%s: usage: %s NBYTES\n", argv[0], argv[0]);
return 1;
}
/* If there's no work to do, don't worry about which library to use. */
if (nbytes == 0)
return 0;
/* Now that we know we have work to do, arrange to use the
appropriate library. */
void (*initialize) (void);
unsigned long long (*rand64) (void);
void (*finalize) (void);
if (rdrand_supported ())
{
initialize = hardware_rand64_init;
rand64 = hardware_rand64;
finalize = hardware_rand64_fini;
}
else
{
initialize = software_rand64_init;
rand64 = software_rand64;
finalize = software_rand64_fini;
}
initialize ();
int wordsize = sizeof rand64 ();
int output_errno = 0;
do
{
unsigned long long x = rand64 ();
int outbytes = nbytes < wordsize ? nbytes : wordsize;
if (!writebytes (x, outbytes))
{
output_errno = errno;
break;
}
nbytes -= outbytes;
}
while (0 < nbytes);
if (fclose (stdout) != 0)
output_errno = errno;
if (output_errno)
{
errno = output_errno;
perror ("output");
}
finalize ();
return !!output_errno;
}
```