// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package asmgen

var ArchAMD64 = &Arch{
	Name:      "amd64",
	WordBits:  64,
	WordBytes: 8,

	regs: []string{
		"BX", "SI", "DI",
		"R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15",
		"AX", "DX", "CX", // last to leave available for hinted allocation
	},
	op3:              x86Op3,
	hint:             x86Hint,
	memOK:            true,
	subCarryIsBorrow: true,

	// Note: Not setting memIndex, because code generally runs faster
	// if we avoid the use of scaled-index memory references,
	// particularly in ADX instructions.

	options: map[Option]func(*Asm, string){
		OptionAltCarry: amd64JmpADX,
	},

	mov:      "MOVQ",
	adds:     "ADDQ",
	adcs:     "ADCQ",
	subs:     "SUBQ",
	sbcs:     "SBBQ",
	lsh:      "SHLQ",
	lshd:     "SHLQ",
	rsh:      "SHRQ",
	rshd:     "SHRQ",
	and:      "ANDQ",
	or:       "ORQ",
	xor:      "XORQ",
	neg:      "NEGQ",
	lea:      "LEAQ",
	addF:     amd64Add,
	mulWideF: x86MulWide,

	addWords: "LEAQ (%[2]s)(%[1]s*8), %[3]s",

	jmpZero:       "TESTQ %[1]s, %[1]s; JZ %[2]s",
	jmpNonZero:    "TESTQ %[1]s, %[1]s; JNZ %[2]s",
	loopBottom:    "SUBQ $1, %[1]s; JNZ %[2]s",
	loopBottomNeg: "ADDQ $1, %[1]s; JNZ %[2]s",
}

func amd64JmpADX( *Asm,  string) {
	.Printf("\tCMPB ·hasADX(SB), $0; JNZ %s\n", )
}

func amd64Add( *Asm, ,  Reg,  Reg,  Carry) bool {
	if .Enabled(OptionAltCarry) {
		// If OptionAltCarry is enabled, the generator is emitting ADD instructions
		// both with and without the AltCarry flag set; the AltCarry flag means to
		// use ADOX. Otherwise we have to use ADCX.
		// Using regular ADD/ADC would smash both carry flags,
		// so we reject anything we can't handled with ADCX/ADOX.
		if &UseCarry != 0 && &(SetCarry|SmashCarry) != 0 {
			if &AltCarry != 0 {
				.op3("ADOXQ", , , )
			} else {
				.op3("ADCXQ", , , )
			}
			return true
		}
		if &(SetCarry|UseCarry) == SetCarry && .IsZero() &&  ==  {
			// Clearing carry flag. Caller will add EOL comment.
			.Printf("\tTESTQ AX, AX\n")
			return true
		}
		if  != KeepCarry {
			.Fatalf("unsupported carry")
		}
	}
	return false
}

// The x86-prefixed functions are shared with Arch386 in 386.go.

func x86Op3( string) bool {
	// As far as a.op3 is concerned, there are no 3-op instructions.
	// (We print instructions like MULX ourselves.)
	return false
}

func x86Hint( *Asm,  Hint) string {
	switch  {
	case HintShiftCount:
		return "CX"
	case HintMulSrc:
		if .Enabled(OptionAltCarry) { // using MULX
			return "DX"
		}
		return "AX"
	case HintMulHi:
		if .Enabled(OptionAltCarry) { // using MULX
			return ""
		}
		return "DX"
	}
	return ""
}

func x86Suffix( *Asm) string {
	// Note: Not using a.Arch == Arch386 to avoid init cycle.
	if .Arch.Name == "386" {
		return "L"
	}
	return "Q"
}

func x86MulWide( *Asm, , , ,  Reg) {
	if .Enabled(OptionAltCarry) {
		// Using ADCX/ADOX; use MULX to avoid clearing carry flag.
		if .name != "DX" {
			if .name != "DX" {
				.Fatalf("mul src1 or src2 must be DX")
			}
			 = 
		}
		.Printf("\tMULXQ %s, %s, %s\n", , , )
		return
	}

	if .name != "AX" {
		if .name != "AX" {
			.Fatalf("mulwide src1 or src2 must be AX")
		}
		 = 
	}
	if .name != "AX" {
		.Fatalf("mulwide dstlo must be AX")
	}
	if .name != "DX" {
		.Fatalf("mulwide dsthi must be DX")
	}
	.Printf("\tMUL%s %s\n", x86Suffix(), )
}