#    This file is part of Metasm, the Ruby assembly manipulation suite
#    Copyright (C) 2006-2009 Yoann GUILLOT
#
#    Licence is LGPL, see LICENCE in the top-level directory


require 'metasm/main'
require 'metasm/cpu/ia32'

module Metasm

# The x86_64, 64-bit extension of the x86 CPU (x64, em64t, amd64...)
class X86_64 < Ia32
	# FpReg, SegReg, Farptr unchanged

	# XMM extended to 16 regs, YMM
	class SimdReg < Ia32::SimdReg
		double_map  64 => (0..7).map { |n| "mm#{n}" },
			   128 => (0..15).map { |n| "xmm#{n}" },
			   256 => (0..15).map { |n| "ymm#{n}" }

		def val_enc
			@val & 7
		end

		def val_rex
			@val >> 3
		end
	end

	# general purpose registers, all sizes
	# 8 new gprs (r8..r15), set bit R in the REX prefix to reference them (or X/B if in ModRM)
	# aonethusaontehsanothe with 8bit subreg: with no rex prefix, refers to ah ch dh bh (as usual)
	#  but whenever the prefix is present, those become unavailable and encodie spl..dil (low byte of rsp/rdi)
	class Reg < Ia32::Reg
		double_map  8 => %w{ al  cl  dl  bl spl bpl sil dil r8b r9b r10b r11b r12b r13b r14b r15b ah ch dh bh},
			   16 => %w{ ax  cx  dx  bx  sp  bp  si  di r8w r9w r10w r11w r12w r13w r14w r15w},
			   32 => %w{eax ecx edx ebx esp ebp esi edi r8d r9d r10d r11d r12d r13d r14d r15d eip},
			   64 => %w{rax rcx rdx rbx rsp rbp rsi rdi r8  r9  r10  r11  r12  r13  r14  r15  rip}

		Sym = @i_to_s[64].map { |s| s.to_sym }

		# returns a symbolic representation of the register:
		# cx => :rcx & 0xffff
		# ah => (:rax >> 8) & 0xff
		# XXX in x64, 32bits operations are zero-extended to 64bits (eg mov rax, 0x1234_ffff_ffff ; add eax, 1 => rax == 0
		def symbolic(di=nil)
			s = Sym[@val]
			s = di.next_addr if s == :rip and di
			if @sz == 8 and to_s[-1] == ?h
				Expression[[Sym[@val-16], :>>, 8], :&, 0xff]
			elsif @sz == 8
				Expression[s, :&, 0xff]
			elsif @sz == 16
				Expression[s, :&, 0xffff]
			elsif @sz == 32
				Expression[s, :&, 0xffffffff]
			else
				s
			end
		end

		# checks if two registers have bits in common
		def share?(other)
			raise 'TODO'
			# XXX TODO wtf does formula this do ?
			other.val % (other.sz >> 1) == @val % (@sz >> 1) and (other.sz != @sz or @sz != 8 or other.val == @val)
		end

		# returns the part of @val to encode in an instruction field
		def val_enc
			if @sz == 8 and @val >= 16; @val-12	# ah, bh, ch, dh
			elsif @val >= 16			# rip
			else @val & 7				# others
			end
		end

		# returns the part of @val to encode in an instruction's rex prefix
		def val_rex
			if @sz == 8 and @val >= 16		# ah, bh, ch, dh: rex forbidden
			elsif @val >= 16			# rip
			else @val >> 3				# others
			end
		end
	end

	# ModRM represents indirections (eg dword ptr [eax+4*ebx+12h])
	# 16bit mode unavailable in x64
	# opcodes use 64bit addressing by default, use adsz override (67h) prefix to switch to 32
	# immediate values are encoded as :i32 sign-extended to 64bits
	class ModRM < Ia32::ModRM
		# mod 0/1/2 m 4 => sib
		# mod 0 m 5 => rip+imm
		# sib: i 4 => no index, b 5 => no base
	end

	class DbgReg < Ia32::DbgReg
		simple_map((0..15).map { |i| [i, "dr#{i}"] })
	end

	class CtrlReg < Ia32::CtrlReg
		simple_map((0..15).map { |i| [i, "cr#{i}"] })
	end

	class TstReg < Ia32::TstReg
		simple_map((0..15).map { |i| [i, "tr#{i}"] })
	end

	# Create a new instance of an X86 cpu
	# arguments (any order)
	# - instruction set (386, 486, sse2...) [latest]
	# - endianness [:little]
	def initialize(*a)
		super(:latest)
		@size = 64
		a.delete @size
		@endianness = (a & [:big, :little]).first || :little
		a.delete @endianness
		@family = a.pop || :latest
		raise "Invalid arguments #{a.inspect}" if not a.empty?
		raise "Invalid X86_64 family #{@family.inspect}" if not respond_to?("init_#@family")
	end

	# defines some preprocessor macros to say who we are:
	# TODO
	def tune_prepro(pp)
		super(pp, :itsmeX64)	# ask Ia32's to just call super()
		pp.define_weak('_M_AMD64')
		pp.define_weak('_M_X64')
		pp.define_weak('__amd64__')
		pp.define_weak('__x86_64__')
	end

	def str_to_reg(str)
		# X86_64::Reg != Ia32::Reg
		Reg.s_to_i.has_key?(str) ? Reg.from_str(str) : SimdReg.s_to_i.has_key?(str) ? SimdReg.from_str(str) : nil
	end

	def shortname
		"x64#{'_be' if @endianness == :big}"
	end
end

X64 = X86_64
AMD64 = X86_64

end
