implementation module MIPS.MIPS32

import StdEnv

import Text

instance toString Line
where
	toString (StartSection s) = "\t." +++ s
	toString (Align i) = "\t.align\t" +++ toString i
	toString (Label l) = l +++ ":"
	toString (Global l) = "\t.globl\t" +++ l
	toString (Instr i) = "\t" +++ toString i
	toString (RawByte i) = "\t.byte\t" +++ toString i
	toString (RawWord i) = "\t.word\t" +++ toString i
	toString (RawWordLabel l) = "\t.word\t" +++ l
	toString (RawAscii s) = concat3 "\t.ascii\t\"" s "\"" // TODO: escaping

instance toString Instruction
where
	toString (LoadByte signed rt offset base) =
		memInstrS (if (signed=:Signed) "lb" "lbu") rt (checkOffset offset) base
	toString (LoadHalfword signed rt offset base) =
		memInstrS (if (signed=:Signed) "lh" "lhu") rt (checkOffset offset) base
	toString (LoadWord rt offset base) =
		memInstrS "lw" rt (checkOffset offset) base
	toString (StoreByte rt offset base) =
		memInstrS "sb" rt (checkOffset offset) base
	toString (StoreHalfword rt offset base) =
		memInstrS "sh" rt (checkOffset offset) base
	toString (StoreWord rt offset base) =
		memInstrS "sw" rt (checkOffset offset) base

	toString (AddImmediate signed rt rs imm) =
		immediateInstrS (if (signed=:Signed) "addi" "addiu") rt rs (checkImmediate signed imm)
	toString (AndImmediate rt rs imm) =
		immediateInstrS "andi" rt rs (checkImmediate Unsigned imm)
	toString (LoadUpperImmediate rt imm) =
		concat ["lui\t",toString rt,",",toString (checkImmediate Unsigned imm)]
	toString (OrImmediate rt rs imm) =
		immediateInstrS "ori" rt rs (checkImmediate Unsigned imm)
	toString (XorImmediate rt rs imm) =
		immediateInstrS "xori" rt rs (checkImmediate Unsigned imm)

	toString (AddWord signed rd rs rt) =
		threeRegInstrS (if (signed=:Signed) "add" "addu") rd rs rt
	toString (AndWord rd rs rt) =
		threeRegInstrS "and" rd rs rt
	toString (NorWord rd rs rt) =
		threeRegInstrS "nor" rd rs rt
	toString (OrWord rd rs rt) =
		threeRegInstrS "or" rd rs rt
	toString (SubWord signed rd rs rt) =
		threeRegInstrS (if (signed=:Signed) "sub" "subu") rd rs rt
	toString (XorWord rd rs rt) =
		threeRegInstrS "xor" rd rs rt

	toString (Jump link target) =
		instr +++ toString target
	where
		instr = case (link, target) of
			(NoLink, Direct _) -> "j\t"
			(Link, Direct _) -> "jal\t"
			(NoLink, Register _) -> "jr\t"
			(Link, Register _) -> "jalr\t"
	toString (BranchOn1 (BCGeZero link) rs offset) =
		branch1InstrS (if (link=:Link) "bgezal" "bgez") rs (checkOffset offset)
	toString (BranchOn1 BCGtZero rs offset) =
		branch1InstrS "bgtz" rs (checkOffset offset)
	toString (BranchOn1 BCLeZero rs offset) =
		branch1InstrS "blez" rs (checkOffset offset)
	toString (BranchOn1 (BCLtZero link) rs offset) =
		branch1InstrS (if (link=:Link) "bltzal" "bltz") rs (checkOffset offset)
	toString (BranchOn2 cond rs rt offset) =
		immediateInstrS (if (cond=:BCEq) "beq" "bne") rs rt (Immediate (checkOffset offset))

	toString (Break arg) =
		"break\t" +++ toString arg
	toString (Syscall arg) =
		"syscall\t" +++ toString arg

	toString (LoadAddress rd imm) =
		concat4 "la\t" (toString rd) "," (toString imm)
	toString Nop =
		"nop"

instance toString JumpTarget
where
	toString (Direct imm)
		= toString imm
	toString (Register r)
		= toString r

instance toString Immediate
where
	toString (Immediate i)
		= toString i
	toString (Address offset label)
		| offset == 0
			= label
		| offset > 0
			= concat3 label "+" (toString offset)
			= concat3 label "-" (toString offset)

memInstrS :: !String !Register !Offset !Base -> String
memInstrS opcode rt 0 base = concat [opcode,"\t",toString rt,",(",toString base,")"]
memInstrS opcode rt offset base = concat [opcode,"\t",toString rt,",",toString offset,"(",toString base,")"]

immediateInstrS :: !String !TargetRegister !SourceRegister !Immediate -> String
immediateInstrS opcode rt rs imm = concat [opcode,"\t",toString rt,",",toString rs,",",toString imm]

threeRegInstrS :: !String !DestinationRegister !SourceRegister !TargetRegister -> String
threeRegInstrS opcode rd rs rt = concat [opcode,"\t",toString rd,",",toString rs,",",toString rt]

branch1InstrS :: !String !SourceRegister !Offset -> String
branch1InstrS opcode rs offset = concat [opcode,"\t",toString rs,",",toString offset]

instance toString Register
where
	toString reg = toString` (checkReg reg)
	where
		/* Sorted by estimated frequency for efficiency */
		toString` (T n) = "$t" +++ toString n
		toString` (S n) = "$s" +++ toString n
		toString` (A n) = "$a" +++ toString n
		toString` R0 = "$0"
		toString` GP = "$gp"
		toString` SP = "$sp"
		toString` FP = "$fp"
		toString` RA = "$ra"
		toString` AT = "$at"
		toString` V0 = "$v0"
		toString` V1 = "$v1"
		toString` K0 = "$k0"
		toString` K1 = "$k1"

checkReg :: !Register -> Register
checkReg (T n) | not (0 <= n && n <= 9)
	= abort ("non-existing register $t" +++ toString n +++ "\n")
checkReg (S n) | not (0 <= n && n <= 7)
	= abort ("non-existing register $s" +++ toString n +++ "\n")
checkReg (A n) | not (0 <= n && n <= 3)
	= abort ("non-existing register $a" +++ toString n +++ "\n")
checkReg reg
	= reg

checkOffset :: !Int -> Int
checkOffset offset
	| 0 <= offset && offset <= 0x7fff
		= offset
	| offset >= -0x8000
		= offset bitand 0xffff
		= abort ("offset " +++ toString offset +++ " out of bounds\n")

checkImmediate :: !Signedness !Immediate -> Immediate
checkImmediate Signed i=:(Immediate imm)
	| 0 <= imm && imm <= 0x7fff
		= i
	| imm >= -0x8000
		= i
		= abort ("signed immediate " +++ toString imm +++ " out of bounds\n")
checkImmediate Unsigned i=:(Immediate imm)
	| 0 <= imm && imm <= 0xffff
		= i
		= abort ("unsigned immediate " +++ toString imm +++ " out of bounds\n")