# Usage
# $cfabuild/driver/cfa -nodebug -quiet -c -x c /dev/null && rm null.o || echo Need to fix \$cfabuild
# make CFABUILD=$cfabuild -j8															     											# compile
# make measurement  CFA_APILEVELS=ll OPERATIONS='pta peq pbv'                             CORPORI='corpus-100-*-1.txt corpus-1-*-1.txt'	# append-pbv
# make measurement2 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=cfa CFA_SHARINGS=share   CORPORI='corpus-1-*-1.txt'						# allocate-speed-cfa
# make measurement2 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=stl CFA_EXPANSIONS=-1.0  CORPORI='corpus-1-*-1.txt'						# allocate-speed-stl
# make measurement3 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=cfa CFA_SHARINGS=share   CORPORI='corpus-1-*-1.txt'						# allocate-space-cfa
# make measurement3 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=stl CFA_EXPANSIONS=-1.0  CORPORI='corpus-1-*-1.txt'						# allocate-space-stl
# make measurement4 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=cfa CFA_EXPANSIONS=0.2   CFA_SHARINGS=share   CORPORI='corpus-1-*-1.txt'	# allocate-attrib-cfa
# make measurement4 CFA_APILEVELS=ll OPERATIONS='pall' PLATFORMS=stl CFA_EXPANSIONS=-1.0                       CORPORI='corpus-1-*-1.txt'	# allocate-attrib-stl

LIBCFA = $(cfabuild)/libcfa/*/src/.libs/libcfa.so

CFA = $(cfabuild)/driver/cfa
PERFFLAGS_CFA = -nodebug -O2
PERFFLAGS_CXX = -DNDEBUG -O2 -Wl,--no-as-needed -ldl


# function: convert to upper case
define uc
$(shell echo $1 | tr  '[:lower:]' '[:upper:]')
endef

# function: project numbered element of filename named by any-delimited tuple
# (call proj,q-w-e-r.txt,-,4) is 4.txt
# (call proj,q-w-e-r.foo.txt,.,1) is q-w-e-r
define proj
$(word $3,$(subst $2, ,$1))
endef

# function: project numbered element of filename named by hyphen-delimited tuple
# (call hyphProj,q-w-e-r.txt,4) is r.txt
define hyphProj
$(call proj,$1,-,$2)
endef

# function: drop file extension and project element
# (call bnHyphProj,q-w-e-r.txt,4) is r
define bnHyphProj
$(call hyphProj,$(basename $1),$2)
endef

# function: drop file extension, project element and uppercase it
# (call ucBnhyphProj,q-w-e-r.txt,4) is R
define ucBnHyphProj
$(call uc,$(call bnHyphProj,$1,$2))
endef

# function: cross two lists, adding given delimiters
# (call delCross,+,a b c,1 2) is a+1 a+2 b+1 b+2 c+1 c+2
define delCross
$(foreach x,$2,$(foreach xs,$3,$(x)$1$(xs)))
endef

define delCross3
$(call delCross,$1,$2,$(call delCross,$1,$3,$4))
endef

define delCross4
$(call delCross,$1,$2,$(call delCross3,$1,$3,$4,$5))
endef

define delCross5
$(call delCross,$1,$2,$(call delCross4,$1,$3,$4,$5,$6))
endef

# function: cross two lists, adding hyphen delimiters
# (call hyphCross,a b c,1 2) is a-1 a-2 b-1 b-2 c-1 c-2
define hyphCross
$(call delCross,-,$1,$2)
endef

define hyphCross3
$(call delCross3,-,$1,$2,$3)
endef

define hyphCross4
$(call delCross4,-,$1,$2,$3,$4)
endef

define hyphCross5
$(call delCross5,-,$1,$2,$3,$4,$5)
endef

OPERATIONS?=pta peq pbv pall #pno
ALLOCS?=reuse fresh
CFA_APILEVELS?=hl ll
CFA_SHARINGS?=share noshare
PLATFORMS?=cfa stl #buhr94

ifneq ($(filter cfa,$(PLATFORMS)),)
    CFA_APIS=$(call hyphCross,$(CFA_APILEVELS),$(CFA_SHARINGS))
endif

ifneq ($(filter stl,$(PLATFORMS)),)
    STL_APIS=na-na
endif

ifneq ($(filter buhr94,$(PLATFORMS)),)
    BUHR94_APIS=na-na
endif

APIS = $(CFA_APIS) $(STL_APIS) $(BUHR94_APIS)

OPERATIONS_USING_ALLOCS=pta peq
define enrichOperationsAllocs
$(call hyphCross3,$(filter peq pta,$(OPERATIONS)),$1,$(ALLOCS)) $(call hyphCross3,$(filter-out peq pta,$(OPERATIONS)),$1,na)
endef

CFA_PERFPROGS=$(call hyphCross,perfexp-cfa,$(call enrichOperationsAllocs,$(CFA_APIS)))
STL_PERFPROGS=$(call hyphCross,perfexp-stl,$(call enrichOperationsAllocs,$(STL_APIS)))
BUHR94_PERFPROGS=$(call hyphCross,perfexp-buhr94,$(call enrichOperationsAllocs,$(BUHR94_APIS)))

PERFPROGS = $(CFA_PERFPROGS) $(STL_PERFPROGS) $(BUHR94_PERFPROGS)

all : $(PERFPROGS)

PP_SPLIT := $(shell echo "${PERFPROGS}" | sed -e 's/ /\\n/g')
echoPerfProgs:
	echo -e '$(PP_SPLIT)'

perfexp-%.o: API=$(call ucBnHyphProj,$@,2)
perfexp-%.o: OPERATION=$(call ucBnHyphProj,$@,3)
perfexp-%.o: CFA_APILEVEL=$(call ucBnHyphProj,$@,4)
perfexp-%.o: CFA_SHARING=$(call ucBnHyphProj,$@,5)
perfexp-%.o: ALLOC=$(call ucBnHyphProj,$@,6)
perfexp-%.o: SCENARIO_SWITCH=-DIMPL_$(API)_$(CFA_APILEVEL)_$(CFA_SHARING) -DOP_$(OPERATION) -DALLOC_$(ALLOC)

perfexp-cfa-%.o: CMD=$(CFA) -c $(PERFFLAGS_CFA) $< -o $@ $(SCENARIO_SWITCH)
perfexp-stl-%.o: CMD=$(CXX) -c -xc++ $(PERFFLAGS_CXX) $< -o $@ $(SCENARIO_SWITCH)
perfexp-buhr94-%.o: CMD=$(CXX) -xc++ -c $(PERFFLAGS_CXX) $< -o $@ $(SCENARIO_SWITCH)

perfexp-cfa-peq-%.o: prog.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pta-%.o: prog.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pbv-%.o: prog-passbyval.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pb%.o: prog-passbyX.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pfi-%.o: prog-find.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pall-%.o: prog-allocn.cfa $(LIBCFA)
	$(CMD)
perfexp-cfa-pno-%.o: prog-normalize.cfa $(LIBCFA)
	$(CMD)
perfexp-stl-peq-%.o: prog.cfa
	$(CMD)
perfexp-stl-pta-%.o: prog.cfa
	$(CMD)
perfexp-stl-pbv-%.o: prog-passbyval.cfa
	$(CMD)
perfexp-stl-pfi-%.o: prog-find.cfa
	$(CMD)
perfexp-stl-pall-%.o: prog-allocn.cfa
	$(CMD)
perfexp-stl-pno-%.o: prog-normalize.cfa
	$(CMD)
perfexp-buhr94-peq-%.o: prog.cfa buhr94-string.o buhr94-VbyteSM.o
	$(CMD)
perfexp-buhr94-pta-%.o: prog.cfa buhr94-string.o buhr94-VbyteSM.o
	$(CMD)
perfexp-buhr94-pta-%.o: prog-passbyval.cfa buhr94-string.o buhr94-VbyteSM.o
	$(CMD)
perfexp-buhr94-pall-%.o: prog-allocn.cfa buhr94-string.o buhr94-VbyteSM.o
	$(CMD)
perfexp-buhr94-pno-%.o: prog-normalize.cfa buhr94-string.o buhr94-VbyteSM.o
	$(CMD)

# one of the pbx cases also needs to link with not_string_res.o (handling manually)
perfexp-cfa-%: perfexp-cfa-%.o $(LIBCFA)
	$(CFA) $(PERFFLAGS_CFA) $< -o $@  
perfexp-stl-%: perfexp-stl-%.o $(LIBCFA)
	$(CFA) $(PERFFLAGS_CFA) $< /lib/x86_64-linux-gnu/libstdc++.so.6 -o $@
perfexp-buhr94-% : perfexp-buhr94-%.o buhr94-string.o buhr94-VbyteSM.o
	$(CXX) $(PERFFLAGS_CXX) $^ -o $@

buhr94-string.o:
	$(CXX) -xc++ -c $(PERFFLAGS_CXX) ~/usys1/sm/string/StringSharing/src/string.cc -o $@

buhr94-VbyteSM.o:
	$(CXX) -xc++ -c $(PERFFLAGS_CXX) ~/usys1/sm/string/StringSharing/src/VbyteSM.cc -o $@

clean:
	rm -f *.o perfexp*

MEASURE = $(PERFPROGS)

CORPUS_MEANLEN_MODE?=auto #manual
CORPUS_MEANLEN_START?=1
CORPUS_MEANLEN_END?=500
CORPUS_MEANLEN_STEPS_PER_DOUBLE?=1
CORPUS_MEANLEN_MANVALS?=20 50 100 200 500
COPRUS_NSTRSS?=100
CORPUS_RELSCALES?=1.0
CORPUS_SEEDS?=9876
CORPUS_OFFSET_INSTRS?=t0

CORPUS_MEANLENS_AUTO=${shell python3 gen-size-steps.py ${CORPUS_MEANLEN_START} ${CORPUS_MEANLEN_END} ${CORPUS_MEANLEN_STEPS_PER_DOUBLE}}
CORPUS_MEANLENS=\
	$(if $(filter auto,$(CORPUS_MEANLEN_MODE)),$(CORPUS_MEANLENS_AUTO), \
	$(if $(filter manual,$(CORPUS_MEANLEN_MODE)),$(CORPUS_MEANLEN_MANVALS), \
	ERROR))
CORPUS_SERIES_IDS=$(call delCross3,+,$(CORPUS_RELSCALES),$(CORPUS_SEEDS),$(CORPUS_OFFSET_INSTRS))
CORPUS_SLUGS=$(call hyphCross4,corpus,$(COPRUS_NSTRSS),$(CORPUS_MEANLENS),$(CORPUS_SERIES_IDS))
CORPORI=$(call delCross,.,$(CORPUS_SLUGS),txt)

# function to interpret an offset instruction for a given mean-length,
# producing the pair of arguments for locn and offset
#
# An offset instruction is an opcode and natural-number argument.
# The opcode is either (t)ake or (l)eave.
# The argument is how much length to take from / leave behind.
# The resulting amount is deducted from the mean-length and returned in offset.
# The remaining mean-length is returned in locn.
#
# $(call interpOffsetInstr,20,l1) = 1 19 (i.e. constant-length 20)
# $(call interpOffsetInstr,20,l5) = 5 15
# $(call interpOffsetInstr,20,t5) = 15 5
# $(call interpOffsetInstr,20,t0) = 20 0 (i.e. full variability)
define interpOffsetInstr
$(strip
	$(eval qty := $(subst t,,$(subst l,,$2)))
	$(if $(filter t%,$2), $(shell expr $1 - $(qty)) $(qty),
	$(if $(filter l%,$2), $(qty) $(shell expr $1 - $(qty)),
	ERROR))
)
endef

corpori: $(CORPORI)

make-corpus: make-corpus.cfa
	$(CFA) $< -o $@

corpus-%.txt: COPRUS_NSTRS=$(call bnHyphProj,$@,2)
corpus-%.txt: CORPUS_MEANLEN=$(call bnHyphProj,$@,3)
corpus-%.txt: CORPUS_SERIES_ID=$(call bnHyphProj,$@,4)
corpus-%.txt: CORPUS_RELSCALE=$(call proj,$(CORPUS_SERIES_ID),+,1)
corpus-%.txt: CORPUS_SEED=$(call proj,$(CORPUS_SERIES_ID),+,2)
corpus-%.txt: CORPUS_OFFSET_INSTR=$(call proj,$(CORPUS_SERIES_ID),+,3)
corpus-%.txt: make-corpus
	$(eval CORPUS_OFFSET_INTERPD := $(call interpOffsetInstr,$(CORPUS_MEANLEN),$(CORPUS_OFFSET_INSTR)))
	$(eval CORPUS_LOCN := $(word 1,$(CORPUS_OFFSET_INTERPD)))
	$(eval CORPUS_OFFSET := $(word 2,$(CORPUS_OFFSET_INTERPD)))
	./$< $(COPRUS_NSTRS) $(CORPUS_LOCN) $(CORPUS_RELSCALE) $(CORPUS_SEED) $(CORPUS_OFFSET) > $@

RUN_TASKSET_CPULIST=6

# both ?= and :=
ifeq ($(origin MEASUREMENT_TOFILE), undefined)
MEASUREMENT_TOFILE:=measurement-$(shell date '+%F--%H-%M-%S').output
endif

# General timing
# Output file gets concatenation of program outputs (one line per run)
measurement: $(MEASURE) corpori
	@echo "running $(words $(MEASURE)) programs:" $(MEASURE)
	@echo "... on $(words $(CORPORI)) corpori:" $(CORPORI)
	@echo "... for $(shell expr $(words $(MEASURE)) "*" $(words $(CORPORI))) rows into:" $(MEASUREMENT_TOFILE)
	@for prog in $(MEASURE) ; do \
	    for corpus in $(CORPORI) ; do \
			corpusbody=`cat $$corpus` ; \
			resulttext=`taskset --cpu-list $(RUN_TASKSET_CPULIST) ./$$prog 100 10 $$corpusbody` ; \
			echo $$prog,$$corpus,$$resulttext  >>  $(MEASUREMENT_TOFILE) ; \
			echo $$prog,$$corpus,$$resulttext  ; \
		done ; \
	done

CFA_EXPANSIONS?=0.02 0.05 0.1 0.2 0.4 0.5 0.9 0.98

# Special-case timing with extra IV for CFA_EXPANSIONS
# Output file gets concatenation of program outputs (one line per run)
measurement2: $(MEASURE) corpori
	@echo "running $(words $(MEASURE)) programs:" $(MEASURE)
	@echo "... on $(words $(CORPORI)) corpori:" $(CORPORI)
	@echo "... with $(words $(CFA_EXPANSIONS)) expansions:" $(CFA_EXPANSIONS)
	@echo "... for $(shell expr $(words $(MEASURE)) "*" $(words $(CORPORI)) "*" $(words $(CFA_EXPANSIONS))) rows into:" $(MEASUREMENT_TOFILE)
	@for prog in $(MEASURE) ; do \
	    for corpus in $(CORPORI) ; do \
			for expansion in $(CFA_EXPANSIONS) ; do \
				corpusbody=`cat $$corpus` ; \
				printed=`taskset --cpu-list $(RUN_TASKSET_CPULIST) ./$$prog 1000 1.006 $$expansion 10 $$corpusbody` ; \
				echo $$prog,$$corpus,$$expansion,$$printed  >>  $(MEASUREMENT_TOFILE) ; \
				echo $$prog,$$corpus,$$expansion,$$printed  ; \
			done ; \
		done ; \
	done

# Space, with the IV for CFA_EXPANSIONS
# Runs Mubeen's malloc interceptor to get malloc-request stats
# Output file gets concatenation of the interceptor's output (one line per run); program's output ignorred
# Expect and ignore crashes; they're during shutdown, after everything we care about is over
measurement3: $(MEASURE) corpori
	@echo "running $(words $(MEASURE)) programs:" $(MEASURE)
	@echo "... on $(words $(CORPORI)) corpori:" $(CORPORI)
	@echo "... with $(words $(CFA_EXPANSIONS)) expansions:" $(CFA_EXPANSIONS)
	@echo "... for $(shell expr $(words $(MEASURE)) "*" $(words $(CORPORI))) rows into:" $(MEASUREMENT_TOFILE)
	@for prog in $(MEASURE) ; do \
	    for corpus in $(CORPORI) ; do \
			for expansion in $(CFA_EXPANSIONS) ; do \
				corpusbody=`cat $$corpus` ; \
				LD_PRELOAD=~/plg2/mubeen-stat-shim/malloc/mallocWrappers.so ./$$prog 1000 1.006 $$expansion 10 $$corpusbody ; \
				printed=`tail -n 1 preload_dump.txt` ; \
				echo $$prog $$corpus $$expansion $$printed  >>  $(MEASUREMENT_TOFILE) ; \
				echo $$prog $$corpus $$expansion $$printed  ; \
				rm preload_dump.txt ; \
			done ; \
		done ; \
	done

# Time attribution, with the IV for CFA_EXPANSIONS
# Runs the SUT under perf, then crunches the perf result
# Output file gets concatenation of perf summaries (several lines per run); program's output ignorred
# Expect and ignore output "addr2line: DWARF error: section .debug_info is larger than its filesize!"
measurement4: $(MEASURE) corpori
	@echo "running $(words $(MEASURE)) programs:" $(MEASURE)
	@echo "... on $(words $(CORPORI)) corpori:" $(CORPORI)
	@echo "... with $(words $(CFA_EXPANSIONS)) expansions:" $(CFA_EXPANSIONS)
	@echo "... for $(shell expr $(words $(MEASURE)) "*" $(words $(CORPORI))) rows into:" $(MEASUREMENT_TOFILE)
	@RUNID=`date '+%F--%H-%M-%S'` ; \
	for prog in $(MEASURE) ; do \
	    for corpus in $(CORPORI) ; do \
			for expansion in $(CFA_EXPANSIONS) ; do \
				SLUG=measurement--$$prog--$$corpus--$$expansion--$$RUNID ; \
				corpusbody=`cat $$corpus` ; \
				perf record --call-graph dwarf -m16M ./$$prog 1000 1.006 $$expansion 10 $$corpusbody ; \
				mv perf.data $$SLUG.data ; \
				perf script -i $$SLUG.data > $$SLUG.perf ; \
				~/flamegraph/FlameGraph/stackcollapse-perf.pl $$SLUG.perf > $$SLUG.folded ; \
				~/flamegraph/FlameGraph/flamegraph.pl $$SLUG.folded > $$SLUG.svg ; \
				python3 process-allocn-attrib.py $$SLUG.folded | xargs -L1 echo $$prog $$corpus $$expansion >> $(MEASUREMENT_TOFILE) ; \
			done ; \
		done ; \
	done


#
# Experiment-specific "summary" targets
#

# These all use recursive make invocations, after setting the scoping variables.
# Recursive invocation necessary to recompute dependencies like corpori -> $(CORPORI).
# In the top-level make, the extent of the dependency is computed upfront, using
# default values, overridden by CLI-provided values.
# Setting the value per-target makes it available to other targets' bodies, but it
# does not affect the previously-computed dependencies.
# Exporting the values and working through a child make has the child intialize its
# dependencies using the summary-target-provided values.
# For that to work, the exported variable default values given above must use ?=.

.PHONY: result-append-pbv.csv result-allocate-speed-%.csv result-allocate-speed-cfa.csv

export OPERATIONS
export ALLOCS
export CFA_APILEVELS
export CFA_SHARINGS
export PLATFORMS
export CFA_EXPANSIONS

export CORPUS_MEANLEN_MODE
export CORPUS_MEANLEN_START
export CORPUS_MEANLEN_END
export CORPUS_MEANLEN_STEPS_PER_DOUBLE
export CORPUS_MEANLEN_MANVALS
export COPRUS_NSTRSS
export CORPUS_RELSCALES
export CORPUS_SEEDS
export CORPUS_OFFSET_INSTRS


export MEASUREMENT_TOFILE

result-ANY:
	$(MAKE) $(SUBTARGET)

result-append-pbv.csv: CFA_APILEVELS=ll
result-append-pbv.csv: OPERATIONS=pta peq pbv
result-append-pbv.csv: CORPUS_OFFSET_INSTRS=t0 l1
result-append-pbv.csv: CORPUS_MEANLEN_STEPS_PER_DOUBLE=4
result-append-pbv.csv: CORPUS_SEEDS=501 502
result-append-pbv.csv: SUBTARGET=measurement
result-append-pbv.csv: result-ANY
	cp $(MEASUREMENT_TOFILE) $@

result-allocate-ANY: OPERATIONS=pall
result-allocate-ANY: CORPUS_OFFSET_INSTRS=l15
result-allocate-ANY: CORPUS_MEANLEN_MODE=manual
result-allocate-ANY: CFA_SHARINGS=share
result-allocate-ANY: CORPUS_SEEDS=101 102 103 104 105
result-allocate-ANY: CFA_APILEVELS=ll
result-allocate-ANY: result-ANY

result-allocate-ANY-cfa: PLATFORMS=cfa
result-allocate-ANY-cfa: result-allocate-ANY

result-allocate-ANY-stl: PLATFORMS=stl
result-allocate-ANY-stl: CFA_EXPANSIONS=-1.0
result-allocate-ANY-stl: result-allocate-ANY






result-allocate-speed-cfa.csv: SUBTARGET=measurement2
result-allocate-speed-cfa.csv: result-allocate-ANY-cfa
	cp $(MEASUREMENT_TOFILE) $@

result-allocate-speed-stl.csv: SUBTARGET=measurement2
result-allocate-speed-stl.csv: result-allocate-ANY-stl
	cp $(MEASUREMENT_TOFILE) $@

# result-allocate-space-cfa.ssv: SUBTARGET=measurement3
# result-allocate-space-cfa.ssv: result-allocate-ANY-cfa
# 	cp $(MEASUREMENT_TOFILE) $@

# result-allocate-space-stl.ssv: SUBTARGET=measurement3
# result-allocate-space-stl.ssv: result-allocate-ANY-stl
# 	cp $(MEASUREMENT_TOFILE) $@

# result-allocate-attrib-cfa.ssv: SUBTARGET=measurement4
# result-allocate-attrib-cfa.ssv: result-allocate-ANY-cfa
# 	cp $(MEASUREMENT_TOFILE) $@

# result-allocate-attrib-stl.ssv: SUBTARGET=measurement4
# result-allocate-attrib-stl.ssv: result-allocate-ANY-stl
# 	cp $(MEASUREMENT_TOFILE) $@







# result-allocate-speed-%.csv: SUBTARGET=measurement2
# result-allocate-speed-%.csv: result-allocate-ANY-%
# 	cp $(MEASUREMENT_TOFILE) $@

result-allocate-space-%.ssv: SUBTARGET=measurement3
result-allocate-space-%.ssv: result-allocate-ANY-%
	cp $(MEASUREMENT_TOFILE) $@

result-allocate-attrib-%.ssv: SUBTARGET=measurement4
result-allocate-attrib-%.ssv: result-allocate-ANY-%
	cp $(MEASUREMENT_TOFILE) $@






# .PHONY result-append-pbv.csv result-allocate-ANY-cfa result-allocate-speed-stl.csv
# result-allocate-space-cfa.csv
# result-allocate-space-stl.csv
# result-allocate-attrib-cfa.csv
# result-allocate-attrib-stl.csv
