-----------------------------------------------------------------------------
--
-- CmmLint: checking the correctness of Cmm statements and expressions
--
-- (c) The University of Glasgow 2004
--
-----------------------------------------------------------------------------

module CmmLint (
  cmmLint, cmmLintTop
  ) where

#include "HsVersions.h"

import Cmm
import CLabel		( pprCLabel )
import MachOp
import Outputable
import PprCmm
import Unique		( getUnique )
import Constants	( wORD_SIZE )

import Monad		( when )

-- -----------------------------------------------------------------------------
-- Exported entry points:

cmmLint :: Cmm -> Maybe SDoc
cmmLint (Cmm tops) = runCmmLint $ mapM_ lintCmmTop tops

cmmLintTop :: CmmTop -> Maybe SDoc
cmmLintTop top = runCmmLint $ lintCmmTop top

runCmmLint :: CmmLint a -> Maybe SDoc
runCmmLint l = 
   case unCL l of
	Left err -> Just (ptext SLIT("Cmm lint error:") $$ nest 2 err)
	Right _  -> Nothing

lintCmmTop (CmmProc _info lbl _args blocks)
  = addLintInfo (text "in proc " <> pprCLabel lbl) $
	mapM_ lintCmmBlock blocks
lintCmmTop _other
  = return ()

lintCmmBlock (BasicBlock id stmts)
  = addLintInfo (text "in basic block " <> ppr (getUnique id)) $
	mapM_ lintCmmStmt stmts

-- -----------------------------------------------------------------------------
-- lintCmmExpr

-- Checks whether a CmmExpr is "type-correct", and check for obvious-looking
-- byte/word mismatches.

lintCmmExpr :: CmmExpr -> CmmLint MachRep
lintCmmExpr (CmmLoad expr rep) = do
  lintCmmExpr expr
  when (machRepByteWidth rep >= wORD_SIZE) $
     cmmCheckWordAddress expr
  return rep
lintCmmExpr expr@(CmmMachOp op args) = do
  mapM_ lintCmmExpr args
  if map cmmExprRep args == machOpArgReps op
  	then cmmCheckMachOp op args
	else cmmLintMachOpErr expr
lintCmmExpr (CmmRegOff reg offset)
  = lintCmmExpr (CmmMachOp (MO_Add rep) 
		[CmmReg reg, CmmLit (CmmInt (fromIntegral offset) rep)])
  where rep = cmmRegRep reg
lintCmmExpr expr = 
  return (cmmExprRep expr)

-- Check for some common byte/word mismatches (eg. Sp + 1)
cmmCheckMachOp  op args@[CmmReg reg, CmmLit (CmmInt i _)]
  | isWordOffsetReg reg && isOffsetOp op && i `rem` fromIntegral wORD_SIZE /= 0
  = cmmLintDubiousWordOffset (CmmMachOp op args)
cmmCheckMachOp op [lit@(CmmLit (CmmInt i _)), reg@(CmmReg _)]
  = cmmCheckMachOp op [reg, lit]
cmmCheckMachOp op args
  = return (resultRepOfMachOp op)

isWordOffsetReg (CmmGlobal Sp) = True
isWordOffsetReg (CmmGlobal Hp) = True
isWordOffsetReg _ = False

isOffsetOp (MO_Add _) = True
isOffsetOp (MO_Sub _) = True
isOffsetOp _ = False

-- This expression should be an address from which a word can be loaded:
-- check for funny-looking sub-word offsets.
cmmCheckWordAddress e@(CmmMachOp op [arg, CmmLit (CmmInt i _)])
  | isOffsetOp op && i `rem` fromIntegral wORD_SIZE /= 0
  = cmmLintDubiousWordOffset e
cmmCheckWordAddress e@(CmmMachOp op [CmmLit (CmmInt i _), arg])
  | isOffsetOp op && i `rem` fromIntegral wORD_SIZE /= 0
  = cmmLintDubiousWordOffset e
cmmCheckWordAddress _
  = return ()


lintCmmStmt :: CmmStmt -> CmmLint ()
lintCmmStmt stmt@(CmmAssign reg expr) = do
  erep <- lintCmmExpr expr
  if (erep == cmmRegRep reg)
	then return ()
	else cmmLintAssignErr stmt
lintCmmStmt (CmmStore l r) = do
  lintCmmExpr l
  lintCmmExpr r
  return ()
lintCmmStmt (CmmCall _target _res args _vols) = mapM_ (lintCmmExpr.fst) args
lintCmmStmt (CmmCondBranch e _id)   = lintCmmExpr e >> return ()
lintCmmStmt (CmmSwitch e _branches) = lintCmmExpr e >> return ()
lintCmmStmt (CmmJump e _args)       = lintCmmExpr e >> return ()
lintCmmStmt _other 		    = return ()

-- -----------------------------------------------------------------------------
-- CmmLint monad

-- just a basic error monad:

newtype CmmLint a = CmmLint { unCL :: Either SDoc a }

instance Monad CmmLint where
  CmmLint m >>= k = CmmLint $ case m of 
				Left e -> Left e
				Right a -> unCL (k a)
  return a = CmmLint (Right a)

cmmLintErr :: SDoc -> CmmLint a
cmmLintErr msg = CmmLint (Left msg)

addLintInfo :: SDoc -> CmmLint a -> CmmLint a
addLintInfo info thing = CmmLint $ 
   case unCL thing of
	Left err -> Left (hang info 2 err)
	Right a  -> Right a

cmmLintMachOpErr :: CmmExpr -> CmmLint a
cmmLintMachOpErr expr = cmmLintErr (text "in MachOp application: " $$ 
					nest 2 (pprExpr expr))

cmmLintAssignErr :: CmmStmt -> CmmLint a
cmmLintAssignErr stmt = cmmLintErr (text "in assignment: " $$ 
					nest 2 (pprStmt stmt))

cmmLintDubiousWordOffset :: CmmExpr -> CmmLint a
cmmLintDubiousWordOffset expr
   = cmmLintErr (text "offset is not a multiple of words: " $$
			nest 2 (pprExpr expr))
