123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540 |
- #!/usr/bin/env python3
- import sys
- import CompileInstruction
- #List of already inserted libraries.
- #To prevent multiple insertions of the same library.
- #Global to allow access in recursion
- libraryList = []
- #If the program we assemble is the BDOS operating system
- BDOSos = False
- #If we have to assemble the program as a BDOS user program
- BDOSprogram = False
- #Global offset of program in memory
- programOffset = 0
- #Remove unreachable code
- optimizeSize = False
- def removeFunctionFromCode(parsedLines, toRemove):
- returnList = []
- start = -1
- end = -1
- belowCodeSection = False
- for idx, line in enumerate(parsedLines):
- if line[1][0] == "Int:":
- belowCodeSection = True
- if start == -1:
- if line[1][0] == toRemove + ":":
- start = idx
- #print (start)
- #print(parsedLines[start])
- elif end == -1:
- # continue until next function found
- if (line[1][0][-1] == ':'):
- if not belowCodeSection:
- if ("Label_" not in line[1][0]):
- end = idx
- #print (end)
- #print(parsedLines[end])
- # when we are below the code section, stop at any new label
- else:
- end = idx
- returnList = returnList + parsedLines[0:start]
- returnList = returnList + parsedLines[end:]
- return returnList
- def removeUnreachebleCode(parsedLines):
- #print("orig len:", len(parsedLines))
- returnList = parsedLines
- asm = [x[1] for x in parsedLines]
- functionNames = []
- jumps = []
- for x in asm:
- if len(x) > 0:
- if (x[0][-1] == ':'):
- if ("Label_" not in x[0]):
- functionNames.append(x[0][:-1])
- if (x[0] == "addr2reg"):
- if ("Label_" not in x[1]):
- jumps.append(x[1])
- if (x[0] == "jump"):
- if ("Label_" not in x[1]):
- jumps.append(x[1])
- #for f in functionNames:
- # print(f)
-
- #for j in jumps:
- # print(j)
- unusedFunctions = list((set(functionNames).difference(jumps)).difference(["Main", "Int", "Syscall"]))
- foundUnusedFunctions = len(unusedFunctions)
- for u in unusedFunctions:
- #print(u)
- returnList = removeFunctionFromCode(returnList, u)
- # recursive check
- if foundUnusedFunctions > 0:
- returnList = removeUnreachebleCode(returnList)
- #print("after len:", len(returnList))
- return returnList
- def parseLines(fileName):
- parsedLines = []
- with open(fileName, 'r') as f:
- for i, line in enumerate(f, start=1):
- # do something special in case of a .ds instruction
- if (len(line) > 4 and line.split(" ",maxsplit=1)[0] == ".ds"):
- parsedLines.append((i, ['.ds', line.split(" ",maxsplit=1)[1].rstrip('\n')]))
- else:
- parsedLine = line.strip().split(";",maxsplit=1)[0].split()
- if (parsedLine != []):
- parsedLines.append((i, parsedLine))
- parsedLines.append((0, ['.EOF'])) # add end of file token
- return parsedLines
- def moveDataDown(parsedLines):
- for idx, line in enumerate(parsedLines):
- if (line[1][0] == ".EOF"): # return when gone through entire file
- return parsedLines
- if (line[1][0] == ".data"): # when we found the start of a .data segment
- while (parsedLines[idx][1][0] != ".code" and parsedLines[idx][1][0] != ".rdata" and parsedLines[idx][1][0] != ".bss" and parsedLines[idx][1][0] != ".EOF"): # move all lines to the end until .code, .rdata or .EOF
- parsedLines.append(parsedLines.pop(idx))
- # should not get here
- print("SHOULD NOT GET HERE")
- sys.exit(1)
- return None
- def moveRDataDown(parsedLines):
- for idx, line in enumerate(parsedLines):
- if (line[1][0] == ".EOF"): # return when gone through entire file
- return parsedLines
- if (line[1][0] == ".rdata"): # when we found the start of a .rdata segment
- while (parsedLines[idx][1][0] != ".code" and parsedLines[idx][1][0] != ".data" and parsedLines[idx][1][0] != ".bss" and parsedLines[idx][1][0] != ".EOF"): # move all lines to the end until .code, .data or .EOF
- parsedLines.append(parsedLines.pop(idx))
- # should not get here
- print("SHOULD NOT GET HERE")
- sys.exit(1)
- return None
- def moveBssDown(parsedLines):
- for idx, line in enumerate(parsedLines):
- if (line[1][0] == ".EOF"): # return when gone through entire file
- return parsedLines
- if (line[1][0] == ".bss"): # when we found the start of a .rdata segment
- while (parsedLines[idx][1][0] != ".code" and parsedLines[idx][1][0] != ".data" and parsedLines[idx][1][0] != ".rdata" and parsedLines[idx][1][0] != ".EOF"): # move all lines to the end until .code, .data or .EOF
- parsedLines.append(parsedLines.pop(idx))
- # should not get here
- print("SHOULD NOT GET HERE")
- sys.exit(1)
- return None
- def removeAssemblerDirectives(parsedLines):
- return [line for line in parsedLines if line[1][0] not in [".code", ".rdata", ".data", ".bss", ".EOF"]]
- def insertLibraries(parsedLines):
- returnList = []
- returnList.extend(parsedLines)
- for line in parsedLines:
- if (len(line[1]) == 2):
- if (line[1][0]) == "`include":
- if (line[1][1] not in libraryList):
- libraryList.append(line[1][1])
- insertList = insertLibraries(parseLines(line[1][1])) #recursion to include libraries within libraries
- for i in range(len(insertList)):
- returnList.insert(i, insertList[i])
- return returnList
- def compileLine(line):
- compiledLine = ""
- #check what kind of instruction this line is
- switch = {
- "halt" : CompileInstruction.compileHalt,
- "read" : CompileInstruction.compileRead,
- "write" : CompileInstruction.compileWrite,
- "readintid" : CompileInstruction.compileIntID,
- "push" : CompileInstruction.compilePush,
- "pop" : CompileInstruction.compilePop,
- "jump" : CompileInstruction.compileJump,
- "jumpo" : CompileInstruction.compileJumpo,
- "jumpr" : CompileInstruction.compileJumpr,
- "jumpro" : CompileInstruction.compileJumpro,
- "beq" : CompileInstruction.compileBEQ,
- "bgt" : CompileInstruction.compileBGT,
- "bgts" : CompileInstruction.compileBGTS,
- "bge" : CompileInstruction.compileBGE,
- "bges" : CompileInstruction.compileBGES,
- "bne" : CompileInstruction.compileBNE,
- "blt" : CompileInstruction.compileBLT,
- "blts" : CompileInstruction.compileBLTS,
- "ble" : CompileInstruction.compileBLE,
- "bles" : CompileInstruction.compileBLES,
- "savpc" : CompileInstruction.compileSavPC,
- "reti" : CompileInstruction.compileReti,
- "ccache" : CompileInstruction.compileCcache,
- "or" : CompileInstruction.compileOR,
- "and" : CompileInstruction.compileAND,
- "xor" : CompileInstruction.compileXOR,
- "add" : CompileInstruction.compileADD,
- "sub" : CompileInstruction.compileSUB,
- "shiftl" : CompileInstruction.compileSHIFTL,
- "shiftr" : CompileInstruction.compileSHIFTR,
- "shiftrs" : CompileInstruction.compileSHIFTRS,
- "not" : CompileInstruction.compileNOT,
- "mults" : CompileInstruction.compileMULTS,
- "multu" : CompileInstruction.compileMULTU,
- "multfp" : CompileInstruction.compileMULTFP,
- "slt" : CompileInstruction.compileSLT,
- "sltu" : CompileInstruction.compileSLTU,
- "load" : CompileInstruction.compileLoad,
- "loadhi" : CompileInstruction.compileLoadHi,
- "addr2reg" : CompileInstruction.compileAddr2reg,
- "load32" : CompileInstruction.compileLoad32,
- "nop" : CompileInstruction.compileNop,
- ".dw" : CompileInstruction.compileDw,
- ".dd" : CompileInstruction.compileDd,
- ".db" : CompileInstruction.compileDb,
- ".ds" : CompileInstruction.compileDs,
- ".dl" : CompileInstruction.compileDl,
- "loadlabellow" : CompileInstruction.compileLoadLabelLow,
- "loadlabelhigh" : CompileInstruction.compileLoadLabelHigh,
- "`include" : CompileInstruction.compileNothing,
- ".eof" : CompileInstruction.compileNothing
- }
- try:
- compiledLine = switch[line[0].lower()](line)
- #print errors
- except KeyError:
- #check if line is a label
- if len(line) == 1 and line[0][-1] == ':':
- compiledLine = "Label " + str(line[0])
- #if not a label, raise error
- else:
- raise Exception("Unknown instruction '" + str(line[0]) + "'" )
- return compiledLine
- #compiles lines that can be compiled directly
- def passOne(parsedLines):
- passOneResult = []
- for line in parsedLines:
- try:
- compiledLine = compileLine(line[1])
- #fix instructions that have multiple lines
- if compiledLine.split()[0] == "loadBoth":
- passOneResult.append((line[0], compileLine(["load", compiledLine.split()[2], compiledLine.split()[3]])))
- compiledLine = compileLine(["loadhi", compiledLine.split()[1], compiledLine.split()[3]])
- if compiledLine.split()[0] == "loadLabelHigh":
- passOneResult.append((line[0], "loadLabelLow " + " ".join(compiledLine.split()[1:])))
- if compiledLine.split()[0] == "data":
- for i in compiledLine.split():
- if i != "data":
- passOneResult.append((line[0], i + " //data"))
- else:
- if (compiledLine != "ignore"):
- passOneResult.append((line[0], compiledLine))
- except Exception as e:
- print("Error in line " + str(line[0]) + ": " + " ".join(line[1]))
- print("The error is: {0}".format(e))
- print("Assembler will now exit")
- sys.exit(1)
- return passOneResult
- #reads and removes define statements, stores them into dictionary
- def obtainDefines(content):
- defines = {} #list of definitions with their value
- contentWithoutDefines = [] #lines without defines
- defineLines = [] #lines with defines
- #seperate defines from other lines
- for line in content:
- if line[1][0].lower() == "define":
- #do error checking
- if len(line[1]) != 4 or line[1][2] != "=":
- print("Error in line " + str(line[0]) + ": " + " ".join(line[1]))
- print("Invalid define statement")
- print("Assembler will now exit")
- sys.exit(1)
- defineLines.append(line)
- else:
- contentWithoutDefines.append(line)
- #parse the lines with defines
- for line in defineLines:
- if (line[1][1] in defines):
- print("Error: define " + line[1][1] + " is already defined")
- print("Assembler will now exit")
- sys.exit(1)
- defines.update({line[1][1]:line[1][3]})
- return defines, contentWithoutDefines
- #replace defined words with their value
- def processDefines(defines, content):
- replacedContent = [] #lines where defined words have been replaced
- #for each line, replace the words with their corresponding value if defined
- for line in content:
- replacedContent.append((line[0], [defines.get(word, word) for word in line[1]]))
- return replacedContent
- #adds interrupts, program length placeholder and jump to main
- #skip program length placeholder in case of BDOS program
- #add jump to syscall if BDOS os
- #NOTE: because of a unknown bug in B32P (probably related to return address of interrupt directly after jumping to SDRAM from ROM,
- # the 4th instruction needs to be jump Main as well
- def addHeaderCode(parsedLines):
- if BDOSprogram:
- header = [(0,"jump Main"),(0,"jump Int"), (0,"jump Main"), (0,"jump Main")]
- elif BDOSos:
- header = [(0,"jump Main"),(0,"jump Int"), (0,"LengthOfProgram"), (0,"jump Main"), (0,"jump Syscall")]
- else:
- header = [(0,"jump Main"),(0,"jump Int"), (0,"LengthOfProgram"), (0,"jump Main")]
-
- return header + parsedLines
- #move labels to the next line
- def moveLabels(parsedLines):
- returnList = []
- #move to next line
- # (old iteration) for idx, line in enumerate(parsedLines):
- idx = 0;
- while idx < len(parsedLines):
- line = parsedLines[idx]
- if line[1].lower().split()[0] == "label":
- if idx < len(parsedLines) - 1:
-
- if parsedLines[idx+1][1].lower().split()[0] == "label":
- # (OLD) if we have a label directly below, insert a nop as a quick fix
- #parsedLines.insert(idx+1, (0, "$*" + line[1].split()[1] + "*$ " +"00000000000000000000000000000000 //NOP to quickfix double labels"))
- # if we have a label directly below, insert the label in the first non-label line
- i = 2
- labelDone = False
- while idx+i < len(parsedLines) - 1 and not labelDone:
- if parsedLines[idx+i][1].lower().split()[0] != "label":
- labelDone = True
- parsedLines[idx+i] = (parsedLines[idx+i][0], "$*" + line[1].split()[1] + "*$ " + parsedLines[idx+i][1])
- # add label in comments, but only if the line does not need to have a second pass
- # TODO implement this!
- #if parsedLines[idx+i][1].split()[1][0] == "0" or parsedLines[idx+i][1].split()[1][0] == "1":
- # parsedLines[idx+i][1] = parsedLines[idx+i][1] + " @" + line[1].split()[1][:-1]
- i+=1
- else:
- parsedLines[idx+1] = (parsedLines[idx+1][0], "$*" + line[1].split()[1] + "*$ " + parsedLines[idx+1][1])
- # add label in comments, but only if the line does not need to have a second pass
- # TODO implement this!
- #if parsedLines[idx+1][1].split()[1][0] == "0" or parsedLines[idx+1][1].split()[1][0] == "1":
- # parsedLines[idx+1][1] = parsedLines[idx+1][1] + " @" + line[1].split()[1][:-1]
- else:
- print("Error: label " + line[1].split()[1] + " has no instructions below it")
- print("Assembler will now exit")
- sys.exit(1)
- idx += 1
- #remove original labels
- for line in parsedLines:
- if line[1].lower().split()[0] != "label":
- returnList.append(line)
- return returnList
- #renumbers each line
- def redoLineNumbering(parsedLines):
- returnList = []
- for idx, line in enumerate(parsedLines):
- returnList.append((idx + programOffset, line[1]))
- return returnList
- #removes label prefix and returns a map of labels to line numbers
- #assumes that $* does not occur somewhere else, and that labels are seperated by space
- def getLabelMap(parsedLines):
- labelMap = {}
- returnList = []
- for line in parsedLines:
- numberOfLabels = line[1].count("$*")
- for i in range(numberOfLabels):
- if line[1].split()[i][:2] == "$*" and line[1].split()[i][-3:] == ":*$":
- if (line[1].split()[i][2:-3] in labelMap):
- print("Error: label " + line[1].split()[i][2:-3] + " is already defined")
- print("Assembler will now exit")
- sys.exit(1)
- labelMap[line[1].split()[i][2:-3]] = line[0]
- if line[1].split()[0][:2] == "$*" and line[1].split()[0][-2:] == "*$":
- returnList.append((line[0], line[1].split("*$ ")[-1]))
- else:
- returnList.append(line)
-
- return returnList, labelMap
- #compiles all labels
- def passTwo(parsedLines, labelMap):
- #lines that start with these names should be compiled
- toCompileList = ["jump", "beq", "bgt", "bgts", "bge", "bges", "bne", "blt", "blts", "ble", "bles", "loadlabellow" ,"loadlabelhigh", ".dl"]
- for idx, line in enumerate(parsedLines):
- if line[1].lower().split()[0] in toCompileList:
- for idx2, word in enumerate(line[1].split()):
- if word in labelMap:
- x = line[1].split()
- x[idx2] = str(labelMap.get(word))
- y = compileLine(x)
- parsedLines[idx] = (parsedLines[idx][0], y)
-
- return parsedLines
- #check if all labels are compiled
- def checkNoLabels(parsedLines):
- toCompileList = ["jump", "beq", "bgt", "bgts", "bge", "bges", "bne", "blt", "blts", "ble", "bles", "loadlabellow" ,"loadlabelhigh", ".dl"]
-
- for idx, line in enumerate(parsedLines):
- if line[1].lower().split()[0] in toCompileList:
- labelPos = 0
- if line[1].lower().split()[0] in ["jump", "loadlabellow", "loadlabelhigh", ".dl"]:
- labelPos = 1
- if line[1].lower().split()[0] in ["beq", "bgt", "bgts", "bge", "bges", "bne", "blt", "blts", "ble", "bles"]:
- labelPos = 3
- print("Error: label " + line[1].split()[labelPos] + " is undefined")
- print("Assembler will now exit")
- sys.exit(1)
- if line[1].lower().split()[0] == "label":
- print("Error: label " + line[1].split()[1] + " is used directly after another label")
- print("Assembler will now exit")
- sys.exit(1)
-
- def main():
- #check assemble mode and offset
- global BDOSos
- global BDOSprogram
- global programOffset
- global optimizeSize
- if len(sys.argv) >= 3:
- BDOSprogram = (sys.argv[1].lower() == "bdos")
- if BDOSprogram:
- programOffset = CompileInstruction.getNumber(sys.argv[2])
- if len(sys.argv) >= 2:
- BDOSos = (sys.argv[1].lower() == "os")
- if sys.argv[len(sys.argv)-1] == "-O":
- optimizeSize = True
- #parse lines from file
- parsedLines = parseLines("code.asm")
- #move .data sections down
- parsedLines = moveDataDown(parsedLines)
- #move .rdata sections down
- parsedLines = moveRDataDown(parsedLines)
- #move .bss sections down
- parsedLines = moveBssDown(parsedLines)
- #remove all .code, .data, .rdata, .bss and .EOF lines
- parsedLines = removeAssemblerDirectives(parsedLines)
- #insert libraries
- parsedLines = insertLibraries(parsedLines)
- if optimizeSize:
- parsedLines = removeUnreachebleCode(parsedLines)
- #obtain and remove the define statements
- defines, parsedLines = obtainDefines(parsedLines)
- #replace defined words with their value
- parsedLines = processDefines(defines, parsedLines)
- #do pass one
- passOneResult = passOne(parsedLines)
- #add interrupt code and jumps
- passOneResult = addHeaderCode(passOneResult)
- #move labels to the next line
- passOneResult = moveLabels(passOneResult)
- #redo line numbers for jump addressing
- #from this point no line should become multiple lines in the final code!
- #also no shifting in line numbers!
- passOneResult = redoLineNumbering(passOneResult)
- #removes label prefixes and creates mapping from label to line
- passOneResult, labelMap = getLabelMap(passOneResult)
- #do pass two
- passTwoResult = passTwo(passOneResult, labelMap)
- #check if all labels are processed
- checkNoLabels(passTwoResult)
- #only add length of program if not BDOS user program
- if not BDOSprogram:
- lenString = '{0:032b}'.format(len(passTwoResult)) + " //Length of program"
- #calculate length of program
- passTwoResult[2] = (2, lenString)
- #print result without line numbers
- for line in passTwoResult:
- print(line[1])
- if __name__ == '__main__':
- main()
|