# 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; } ```