#    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/cpu/arm64/main'

module Metasm
class ARM64
	private

	def addop(name, bin, *args)
		o = Opcode.new name, bin
		args.each { |a|
			o.args << a if @valid_args[a]
			o.props[a] = true if @valid_props[a]
			o.props.update a if a.kind_of?(::Hash)
		}

		args.each { |a| o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] }

		@opcode_list << o
	end

	def addop_s31(n, bin, *args)
		addop n, bin, :r_32, *args
		addop n, (1 << 31) | bin, *args
	end

	def addop_s30(n, bin, *args)
		addop n, bin, :r_32, *args
		addop n, (1 << 30) | bin, *args
	end

	def addop_data_shifted(n, bin, *args)
		addop n, bin | (0b00 << 22), :rt, :rn, :rm_lsl_i5, :r_32, *args
		addop n, bin | (0b01 << 22), :rt, :rn, :rm_lsr_i5, :r_32, *args
		addop n, bin | (0b10 << 22), :rt, :rn, :rm_asr_i5, :r_32, *args
		addop n, bin | (0b00 << 22) | (1 << 31), :rt, :rn, :rm_lsl_i6, *args
		addop n, bin | (0b01 << 22) | (1 << 31), :rt, :rn, :rm_lsr_i6, *args
		addop n, bin | (0b10 << 22) | (1 << 31), :rt, :rn, :rm_asr_i6, *args
	end

	def addop_data_imm(n, bin, *args)
		addop n, bin, :rt, :rn, :bitmask_imm, :bitmask_s, :bitmask_r, *args
		addop n, bin | (1 << 31), :rt, :rn, :bitmask_imm, :bitmask_n, :bitmask_s, :bitmask_r,  *args
	end

	def addop_bitfield(n, bin, *args)
		addop n, bin, :rt, :rn, :bitmask, :bitmask_s, :bitmask_r, *args
		addop n, bin | (1 << 31) | (1 << 22), :rt, :rn, :bitmask, :bitmask_s, :bitmask_r,  *args
	end

	# official name => usual name
	OP_DATA_ALIAS = { 'bic' => 'andn', 'orr' => 'or', 'eor' => 'xor' }
	def addop_data_shifted_alias(n, bin, *args)
		if a = OP_DATA_ALIAS[n]
			addop_data_shifted a, bin, *args
		end
		addop_data_shifted n, bin, *args
	end

	def addop_data_imm_alias(n, bin, *args)
		if a = OP_DATA_ALIAS[n]
			addop_data_imm a, bin, *args
		end
		addop_data_imm n, bin, *args
	end

	def addop_store(n, bin, *args)
		addop_s30 n, bin | (0b01 << 10), :rt, :m_rn_s9, :mem_incr => :post
		addop_s30 n, bin | (0b11 << 10), :rt, :m_rn_s9, :mem_incr => :pre
		addop_s30 n, bin | (1 << 21) | (0b10 << 10) | (1 << 14), :rt, :m_rm_extend
		addop_s30 n, bin | (1 << 24), :rt, :m_rn_u12
	end

	OP_CC = %w[eq ne cs cc  mi pl vs vc  hi ls ge lt  gt le al al2]
	def addop_cc(n, bin, *args)
		OP_CC.each_with_index { |e, i|
			args << :stopexec if e[0, 2] == 'al' and args.include?(:setip)
			addop n+e, bin | i, *args
		}
	end

	public
	# ARMv8 64-bits instruction set, aka AArch64
	def init_arm_v8
		@opcode_list = []

		[:stopexec, :setip, :saveip,
		 :r_z,		# reg nr31 = flag ? zero : sp
		 :r_32,		# reg size == 32bit
		 :mem_incr,	# mem dereference is pre/post-increment
		 :mem_sz,	# point to uint32 => 4
		 :pcrel,	# immediate value is pc-relative
		 :pcrel_page,	# immediate value is a page offset, pc-relative
		].each { |p| @valid_props[p] = true }

		[:rn, :rt, :rt2, :rm,
		 :rm_lsl_i6, :rm_lsr_i6, :rm_asr_i6,
		 :rm_lsl_i5, :rm_lsr_i5, :rm_asr_i5,
		 :m_rm_extend, :rm_extend_i3,
		 :i14_5, :i16_5, :il18_5, :i19_5, :i26_0, :i12_10_s1,
		 :i19_5_2_29,
		 :crn, :crm, :i3_16, :i3_5, :i7_5, :i15_5,
		 :m_rn_s7, :m_rn_s9, :m_rn_u12,
		 :bitmask, :bitmask_imm, :cond_12,
		].each { |p| @valid_args[p] = true }

		@fields_mask.update :rn => 0x1f, :rt => 0x1f, :rt2 => 0x1f, :rm => 0x1f,
			:rm_lsl_i6 => 0x7ff, :rm_lsr_i6 => 0x7ff, :rm_asr_i6 => 0x7ff,
			:rm_lsl_i5 => 0x7df, :rm_lsr_i5 => 0x7df, :rm_asr_i5 => 0x7df,
			:m_rm_extend => ((0x1f << 11) | (0xb << 7) | 0x1f), :rm_extend_i3 => 0x7ff,
			:i14_5 => 0x3fff, :i16_5 => 0xffff, :il18_5 => 0x3ffff, :i26_0 => 0x3ffffff,
			:i12_10_s1 => 0x3fff, :i6_10 => 0x3f,
			:s7_15 => 0x7f, :s9_12 => 0x1ff, :u12_10 => 0xfff,
			:i19_5 => 0x7ffff, :i2_29 => 3,
			:i19_5_2_29 => 0x60ffffe0, :cond_12 => 0xf,
			:bitmask_n => 1, :bitmask_s => 0x3f, :bitmask_r => 0x3f,
			:regextend_13 => 7, :i1_12 => 1, :i3_10 => 7,
			:crn => 0xf, :crm => 0xf, :i3_16 => 7, :i3_5 => 7, :i7_5 => 0x7f, :i15_5 => 0x7fff,
			:m_rn_s7  => ((0x7f << 10) | 0x1f),
			:m_rn_s9  => ((0x1ff << 7) | 0x1f),
			:m_rn_u12 => ((0xfff << 5) | 0x1f)

		@fields_shift.update :rn => 5, :rt => 0, :rt2 => 10, :rm => 16,
			:rm_lsl_i6 => 10, :rm_lsr_i6 => 10, :rm_asr_i6 => 10,
			:rm_lsl_i5 => 10, :rm_lsr_i5 => 10, :rm_asr_i5 => 10,
			:m_rm_extend => 5, :rm_extend_i3 => 10,
			:i14_5 => 5, :i16_5 => 5, :il18_5 => 5, :i26_0 => 0,
			:i12_10_s1 => 10, :i6_10 => 10,
			:s7_15 => 15, :s9_12 => 12, :u12_10 => 10,
			:i19_5 => 5, :i2_29 => 29,
			:i19_5_2_29 => 0, :cond_12 => 12,
			:bitmask_n => 22, :bitmask_s => 10, :bitmask_r => 16,
			:regextend_13 => 13, :i1_12 => 12, :i3_10 => 10,
			:crn => 12, :crm => 8, :i3_16 => 16, :i3_5 => 5, :i7_5 => 5, :i15_5 => 5,
			:m_rn_s7 => 5, :m_rn_s9 => 5, :m_rn_u12 => 5

		addop 'adr',  1 << 28, :rt, :i19_5_2_29, :pcrel
		addop 'adrp',(1 << 28) | (1 << 31), :rt, :i19_5_2_29, :pcrel_page

		addop_s31 'cbz',  0b0110100 << 24, :rt, :i19_5, :setip
		addop_s31 'cbnz', 0b0110101 << 24, :rt, :i19_5, :setip
		addop_cc 'b', 0b0101010 << 25, :i19_5, :setip

		addop_s31 'mov', (0b01_01010_00_0 << 21) | (0b11111 << 5), :rt, :rm, :r_z  	# alias for orr rt, 0, rm
		addop_data_shifted_alias 'and',  0b00_01010_00_0 << 21
		addop_data_shifted_alias 'bic',  0b00_01010_00_1 << 21	# and not
		addop_data_shifted_alias 'orr',  0b01_01010_00_0 << 21
		addop_data_shifted_alias 'orn',  0b01_01010_00_1 << 21	# or not
		addop_data_shifted_alias 'eor',  0b10_01010_00_0 << 21
		addop_data_shifted_alias 'eorn', 0b10_01010_00_1 << 21
		addop_data_shifted_alias 'ands', 0b11_01010_00_0 << 21, :r_z	# same as and + set flags
		addop_data_shifted_alias 'bics', 0b11_01010_00_1 << 21, :r_z	# same as bic + set flags

		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b00 << 22), :rn, :rm_lsl_i6, :r_32, :r_z # alias for subs 0, rn, rm
		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b01 << 22), :rn, :rm_lsr_i6, :r_32, :r_z
		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b10 << 22), :rn, :rm_asr_i6, :r_32, :r_z
		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b00 << 22) | (1 << 31), :rn, :rm_lsl_i5, :r_z
		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b01 << 22) | (1 << 31), :rn, :rm_lsr_i5, :r_z
		addop 'cmp', (0b11_01011_00_0 << 21) | (0b11111 << 0) | (0b10 << 22) | (1 << 31), :rn, :rm_asr_i5, :r_z
		addop_s31 'negs', (0b11_01011_00_0 << 21) | (0b11111 << 5), :rt, :rm, :r_z  	# alias for subs rt, 0, rm
		addop_data_shifted_alias 'add', 0b00_01011_00_0 << 21
		addop_data_shifted_alias 'adds',0b01_01011_00_0 << 21, :r_z
		addop_data_shifted_alias 'sub', 0b10_01011_00_0 << 21
		addop_data_shifted_alias 'subs',0b11_01011_00_0 << 21, :r_z

		addop_s31 'add', 0b00_01011_00_1 << 21, :rt, :rn, :rm_extend_i3
		addop_s31 'adds',0b01_01011_00_1 << 21, :rt, :rn, :rm_extend_i3
		addop_s31 'sub', 0b10_01011_00_1 << 21, :rt, :rn, :rm_extend_i3
		addop_s31 'subs',0b11_01011_00_1 << 21, :rt, :rn, :rm_extend_i3

		addop_data_imm_alias 'and', 0b00_100100 << 23
		addop_data_imm_alias 'orr', 0b01_100100 << 23
		addop_data_imm_alias 'eor', 0b10_100100 << 23
		addop_data_imm_alias 'ands',0b11_100100 << 23, :r_z

		addop 'svc',   (0b11010100 << 24) | (0b000 << 21) | (0b00001), :i16_5
		addop 'hvc',   (0b11010100 << 24) | (0b000 << 21) | (0b00010), :i16_5, :stopexec
		addop 'smc',   (0b11010100 << 24) | (0b000 << 21) | (0b00011), :i16_5, :stopexec
		addop 'brk',   (0b11010100 << 24) | (0b001 << 21) | (0b00000), :i16_5, :stopexec
		addop 'hlt',   (0b11010100 << 24) | (0b010 << 21) | (0b00000), :i16_5, :stopexec
		addop 'dcps1', (0b11010100 << 24) | (0b101 << 21) | (0b00001), :i16_5, :stopexec
		addop 'dcps2', (0b11010100 << 24) | (0b101 << 21) | (0b00010), :i16_5, :stopexec
		addop 'dcps3', (0b11010100 << 24) | (0b101 << 21) | (0b00011), :i16_5, :stopexec

		# MSR (immediate)
		addop 'msr_sp',       (0b1101010100 << 22) | (0b0000000100 << 12) | (0b10111111), :crm
		addop 'msr_daif_set', (0b1101010100 << 22) | (0b0000110100 << 12) | (0b11011111), :crm
		addop 'msr_daif_clr', (0b1101010100 << 22) | (0b0000110100 << 12) | (0b11111111), :crm

		# HINT
		addop 'nop',   (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000000011111)
		addop 'sevl',  (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000000111111)
		addop 'sev',   (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000001011111)
		addop 'wfe',   (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000001111111)
		addop 'wfi',   (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000010011111)
		addop 'yield', (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000010111111)
		addop 'nop',   (0b1101010100 << 22) | (0b0000110010 << 12) | (0b000000011111), :i7_5

		addop 'clrex', (0b1101010100 << 22) | (0b0000110011 << 12) | (0b01011111), :crm	# arg ignored
		addop 'dsb',   (0b1101010100 << 22) | (0b0000110011 << 12) | (0b10011111), :crm
		addop 'dmb',   (0b1101010100 << 22) | (0b0000110011 << 12) | (0b10111111), :crm
		addop 'isb',   (0b1101010100 << 22) | (0b0000110011 << 12) | (0b11011111), :crm

		addop 'sys',   (0b1101010100 << 22) | (0b001 << 19), :i3_16, :crn, :crm, :i3_5, :rt
		addop 'sysl',  (0b1101010100 << 22) | (0b101 << 19), :i3_16, :crn, :crm, :i3_5, :rt

		addop 'msr',   (0b1101010100 << 22) | (0b01 << 20), :i15_5, :rt	# i15 = MSR number
		addop 'mrs',   (0b1101010100 << 22) | (0b11 << 20), :i15_5, :rt

		addop_s31 'tbz', (0b0110110 << 24), :rt, :i14_5

		addop 'b',   (0b000101 << 26), :i26_0, :setip, :stopexec
		addop 'bl',  (0b100101 << 26), :i26_0, :setip, :stopexec, :saveip
		addop 'br',  (0b1101011 << 25) | (0b0000 << 21) | (0b11111 << 16), :rn, :setip, :stopexec
		addop 'blr', (0b1101011 << 25) | (0b0001 << 21) | (0b11111 << 16), :rn, :setip, :stopexec, :saveip
		addop 'ret', (0b1101011 << 25) | (0b0010 << 21) | (0b11111 << 16) | (0b11110 << 5), :setip, :stopexec
		addop 'ret', (0b1101011 << 25) | (0b0010 << 21) | (0b11111 << 16), :rn, :setip, :stopexec
		addop 'eret',(0b1101011 << 25) | (0b0100 << 21) | (0b11111 << 16) | (0b11111 << 5), :setip, :stopexec
		addop 'drps',(0b1101011 << 25) | (0b0101 << 21) | (0b11111 << 16) | (0b11111 << 5), :setip, :stopexec

		addop_s31 'mov',  0b0010001 << 24, :rt, :rn			# alias for add rt, rn, 0
		addop_s31 'add',  0b0010001 << 24, :rt, :rn, :i12_10_s1
		addop_s31 'adds', 0b0110001 << 24, :rt, :rn, :i12_10_s1
		addop_s31 'sub',  0b1010001 << 24, :rt, :rn, :i12_10_s1
		addop_s31 'subs', 0b1110001 << 24, :rt, :rn, :i12_10_s1

		addop_s31 'movn', 0b00100101 << 23, :rt, :il18_5
		addop_s31 'mov',  0b10100101 << 23, :rt, :i16_5	# alias movz rt, i16 LSL 0
		addop_s31 'movz', 0b10100101 << 23, :rt, :il18_5
		addop_s31 'movk', 0b11100101 << 23, :rt, :il18_5

		addop_s30 'ldr',  0b00011000 << 24 , :rt, :i19_5
		addop_store 'str',   0b10_111_0_00_00 << 22
		addop_store 'ldr',   0b10_111_0_00_01 << 22
		addop_store 'ldrsw', 0b10_111_0_00_10 << 22
		addop_store 'strb',  0b00_111_0_00_00 << 22
		addop_store 'ldrb',  0b00_111_0_00_01 << 22
		addop_s31 'stp',  0b00_101_0_001_0 << 22, :rt, :rt2, :m_rn_s7, :mem_incr => :post
		addop_s31 'stp',  0b00_101_0_011_0 << 22, :rt, :rt2, :m_rn_s7, :mem_incr => :pre
		addop_s31 'stp',  0b00_101_0_010_0 << 22, :rt, :rt2, :m_rn_s7
		addop_s31 'ldp',  0b00_101_0_001_1 << 22, :rt, :rt2, :m_rn_s7, :mem_incr => :post
		addop_s31 'ldp',  0b00_101_0_011_1 << 22, :rt, :rt2, :m_rn_s7, :mem_incr => :pre
		addop_s31 'ldp',  0b00_101_0_010_1 << 22, :rt, :rt2, :m_rn_s7

		addop_s31 'csel',  (0b0011010100 << 21) | (0b00 << 10), :rt, :rn, :rm, :cond_12, :r_z
		addop_s31 'csinc', (0b0011010100 << 21) | (0b01 << 10), :rt, :rn, :rm, :cond_12, :r_z
		addop_s31 'csinv', (0b1011010100 << 21) | (0b00 << 10), :rt, :rn, :rm, :cond_12, :r_z
		addop_s31 'csneg', (0b1011010100 << 21) | (0b01 << 10), :rt, :rn, :rm, :cond_12, :r_z

		# TODO fix :bitmask decoding
		addop_bitfield 'sbfm', 0b00_100110 << 23
		addop_bitfield 'bfm',  0b01_100110 << 23
		addop_bitfield 'ubfm', 0b10_100110 << 23
	end

	alias init_latest init_arm_v8
end
end
