#include "headers.h"

/* Function prototypes */

static void get_length_of_data_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli,
	HashTable* const restrict string_and_float_literals_hash_table
) attr_nonnull;

static void allocate_memory_for_data_section(
	DataAndCode* const restrict dac,
	HashTable* const restrict string_and_float_literals_hash_table
) attr_nonnull;

static void set_memory_addresses_for_literals(
	DataAndCode* const restrict dac,
	HashTable* const restrict string_and_float_literals_hash_table
) attr_nonnull;

static void get_length_of_code_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli
) attr_nonnull;

static
#if DEBUG
	u64
#else
	void
#endif
allocate_memory_for_code_section(DataAndCode* const restrict dac) attr_nonnull;

static void put_instructions_into_code_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli
) attr_nonnull;

static u64 get_length_of_instructions_generated_by_store_variable_on_stack(const Variable* const restrict variable);
static void store_variable_on_stack(DataAndCode* const restrict dac, Variable* const restrict variable) attr_nonnull;


/* Function definitions */


#if DEBUG
	static void output_variable(Variable* const restrict variable) {
		assert_comparison(variable, !=, NULL);
		
		if (ARROW(variable, name).length) {
			output_string(ARROW(variable, name).string, ARROW(variable, name).length);
		} else {
			output_nullt_string("(internal compiler variable [stack offset: ");
			output_u64(ARROW(variable, stack_offset));
			output_nullt_string("])");
		}
	}
	
	static void output_mid_level_instructions(MidLevelInstructions* const restrict mli) {
		u64 i;
		
		static const char* const opcodes_to_strings[] = {
			/* Assignment */
			"mov_uvar_uval",
			"mov_fvar_fval",
			"mov_fvar_fvar",
			
			/* Multiplication */
			"mul_uvar_uval",
			"mul_fvar_fval",
			
			/* Division */
			"div_uvar_uval",
			"div_fvar_fval",
			
			/* Addition */
			"add_uvar_uval",
			"add_fvar_fval",
			"add_uvar_uvar",
			"add_fvar_fvar",
			
			/* Subtraction */
			"sub_uvar_uval",
			"sub_fvar_fval",
			"sub_uvar_uvar",
			"sub_fvar_fvar"
		};
		
		assert_comparison(mli, !=, NULL);
		
		output_nullt_string("\nMid-level instructions:\n");
		
		for (i = 0; i < ARROW(mli, used_length); ++i) {
			MidLevelInstruction* const instruction = ARRAY_INDEX(ARROW(mli, mid_level_instructions), i);
			
			assert_comparison(ARROW(instruction, opcode), <=, LAST_MLI_OPCODE);
			
			output_nullt_string(TAB_STRING);
			output_nullt_string(opcodes_to_strings[ARROW(instruction, opcode)]);
			
			switch (ARROW(instruction, opcode)) {
				case MLI_OPCODE_MOV_UVAR_UVAL:
				case MLI_OPCODE_MOV_FVAR_FVAL:
				case MLI_OPCODE_MOV_FVAR_FVAR:
				case MLI_OPCODE_MUL_UVAR_UVAL:
				case MLI_OPCODE_MUL_FVAR_FVAL:
				case MLI_OPCODE_DIV_UVAR_UVAL:
				case MLI_OPCODE_DIV_FVAR_FVAL:
				case MLI_OPCODE_ADD_UVAR_UVAL:
				case MLI_OPCODE_ADD_FVAR_FVAL:
				case MLI_OPCODE_ADD_UVAR_UVAR:
				case MLI_OPCODE_ADD_FVAR_FVAR:
				case MLI_OPCODE_SUB_UVAR_UVAL:
				case MLI_OPCODE_SUB_FVAR_FVAL:
				case MLI_OPCODE_SUB_UVAR_UVAR:
				case MLI_OPCODE_SUB_FVAR_FVAR: {
					Variable* variable = (Variable*)ARROW(instruction, argument1);
					output_char(' ');
					output_variable(variable);
				}
				default: {
				}
			}
			
			switch (ARROW(instruction, opcode)) {
				case MLI_OPCODE_MOV_UVAR_UVAL:
				case MLI_OPCODE_MUL_UVAR_UVAL:
				case MLI_OPCODE_DIV_UVAR_UVAL:
				case MLI_OPCODE_ADD_UVAR_UVAL:
				case MLI_OPCODE_SUB_UVAR_UVAL:
					output_nullt_string(", ");
					output_u64(ARROW((OperandNode*)ARROW(instruction, argument2), value).u64_value);
					break;
				
				case MLI_OPCODE_MOV_FVAR_FVAL:
				case MLI_OPCODE_MUL_FVAR_FVAL:
				case MLI_OPCODE_ADD_FVAR_FVAL:
				case MLI_OPCODE_DIV_FVAR_FVAL:
				case MLI_OPCODE_SUB_FVAR_FVAL:
					output_nullt_string(", ");
					output_f64(ARROW((OperandNode*)ARROW(instruction, argument2), value).f64_value);
					break;
				
				case MLI_OPCODE_MOV_FVAR_FVAR:
				case MLI_OPCODE_ADD_UVAR_UVAR:
				case MLI_OPCODE_ADD_FVAR_FVAR:
				case MLI_OPCODE_SUB_UVAR_UVAR:
				case MLI_OPCODE_SUB_FVAR_FVAR: {
					Variable* variable = (Variable*)ARROW(instruction, argument2);
					output_nullt_string(", ");
					output_variable(variable);
					break;
				}
				
				default: {
				}
			}
			
			output_newline();
		}
		
		output_newline();
	}
#endif

/* Function definitions */

void translate_mid_level_instructions_to_cpu_instructions(DataAndCode* const restrict dac, MidLevelInstructions* const restrict mli) {
	/* {string or float literal => addresses of the literal} */
	HashTable string_and_float_literals_hash_table;
	#if DEBUG
		/* The old code length, that is, the length of the code section before it gets rounded up to the next multiple of PAGE_SIZE. */
		u64 old_code_length;
		
		output_mid_level_instructions(mli);
	#endif
	
	hash_table_new(&string_and_float_literals_hash_table, 30, 8, 2.5);
	
	get_length_of_data_section(dac, mli, &string_and_float_literals_hash_table);
	allocate_memory_for_data_section(dac, &string_and_float_literals_hash_table);
	get_length_of_code_section(dac, mli);
	
	/* Yes, this might seem like an odd (or inelegant) use of the preproccessor, but it works ;) */
	#if DEBUG
		old_code_length =
	#endif
	allocate_memory_for_code_section(dac);
	
	put_instructions_into_code_section(dac, mli);
	
	#if DEBUG
		assert_comparison(ARROW(dac, current_instruction_offset), ==, old_code_length);
	#endif
	
	#if DEBUG
		/*
		Ensure that the literal (key) and the dereferenced address of the literal
		that the generated program will use (value) are equal.
		*/
		{
			HashIterator iterator;
			
			hash_iterator_init(&string_and_float_literals_hash_table, &iterator);
			
			while (hash_iterator_next(&iterator)) {
				assert_comparison(iterator.key.f64_value, ==, (*(f64*)(*iterator.value)));
			}
		}
	#endif
	
	#if DEBUG
	{
		HashIterator iterator;
		
		output_nullt_string("String and float literals hash table:\n");
		
		hash_iterator_init(&string_and_float_literals_hash_table, &iterator);
		
		output_nullt_string("{\n");
		
		while (hash_iterator_next(&iterator)) {
			output_nullt_string("   ");
			output_f64(iterator.key.f64_value);
			output_nullt_string(" => ");
			output_u64((u64)(*iterator.value));
			output_nullt_string("\n");
		}
		
		output_nullt_string("}\n");
	}
	#endif
	
	hash_table_del(&string_and_float_literals_hash_table);
}

/* This function gets the length of the data section, which is composed of floating point and string literals. */
static void get_length_of_data_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli,
	HashTable* const restrict string_and_float_literals_hash_table
) {
	u64 i;
	
	assert_comparison(dac, !=, NULL);
	assert_comparison(mli, !=, NULL);
	assert_comparison(string_and_float_literals_hash_table, !=, NULL);
	
	ARROW_ASSIGN(dac, data_length) = 0;
	
	for (i = 0; i < ARROW(mli, used_length); ++i) {
		MidLevelInstruction* const instruction = ARRAY_INDEX(ARROW(mli, mid_level_instructions), i);
		
		switch (ARROW(instruction, opcode)) {
			case MLI_OPCODE_MOV_FVAR_FVAL: {
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				assert_comparison(operand_node, !=, NULL);
				
				if (ARROW(operand_node, value).f64_value == 0.0 || ARROW(operand_node, value).f64_value == 1.0) break;
				
				/* Fall through */
			}
			case MLI_OPCODE_MUL_FVAR_FVAL:
			case MLI_OPCODE_DIV_FVAR_FVAL:
			case MLI_OPCODE_ADD_FVAR_FVAL:
			case MLI_OPCODE_SUB_FVAR_FVAL: {
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				assert_comparison(operand_node, !=, NULL);
				
				if (hash_table_insert_uint(string_and_float_literals_hash_table, ARROW(operand_node, value).u64_value, (void*)1, false)) {
					ARROW_ASSIGN(dac, data_length) += 8;
				}
				ARROW_ASSIGN(operand_node, address_in_memory) = hash_table_get_value_with_uint_key(string_and_float_literals_hash_table, ARROW(operand_node, value).u64_value);
				break;
			}
			default: {
			}
		}
	}
}

static void allocate_memory_for_data_section(
	DataAndCode* const restrict dac,
	HashTable* const restrict string_and_float_literals_hash_table
) {
	assert_comparison(dac, !=, NULL);
	assert_comparison(string_and_float_literals_hash_table, !=, NULL);
	
	if (ARROW(dac, data_length)) {
		ARROW_ASSIGN(dac, start_of_data) = m_alloc(ARROW(dac, data_length));
		
		assert_comparison((u64)ARROW(dac, start_of_data) % 8, ==, 0);
		
		if (unlikely(!ARROW(dac, start_of_data))) {
			output_nullt_string("Unable to allocate memory for floating point and string literals.\n");
			exit(1);
		}
		
		set_memory_addresses_for_literals(dac, string_and_float_literals_hash_table);
	}
}

static void set_memory_addresses_for_literals(
	DataAndCode* const restrict dac,
	HashTable* const restrict string_and_float_literals_hash_table
) {
	u64 data_section_offset;
	HashIterator iterator;
	
	assert_comparison(dac, !=, NULL);
	assert_comparison(string_and_float_literals_hash_table, !=, NULL);
	
	data_section_offset = 0;
	
	hash_iterator_init(string_and_float_literals_hash_table, &iterator);
	
	while (hash_iterator_next(&iterator)) {
		if (iterator.type_of_key == HASH_TYPE_UINT) {
			*iterator.value = ((char*)ARROW(dac, start_of_data)) + data_section_offset;
			*(f64*)(*iterator.value) = iterator.key.f64_value;
			data_section_offset += 8;
		} else {
			assert_comparison(iterator.type_of_key, !=, iterator.type_of_key);
		}
	}
	
	assert_comparison(data_section_offset, ==, ARROW(dac, data_length));
}

static void get_length_of_code_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli
) {
	u64 i;
	
	assert_comparison(dac, !=, NULL);
	assert_comparison(mli, !=, NULL);
	
	ARROW_ASSIGN(dac, code_length) = dac_get_length_of_starting_instructions();
	
	for (i = 0; i < ARROW(mli, used_length); ++i) {
		MidLevelInstruction* const instruction = ARRAY_INDEX(ARROW(mli, mid_level_instructions), i);
		
		switch (ARROW(instruction, opcode)) {
			case MLI_OPCODE_MOV_UVAR_UVAL: {
				/* FIXME */
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_MOV_FVAR_FVAL: {
				Variable* variable;
				OperandNode* operand_node;
				
				variable = (Variable*)ARROW(instruction, argument1);
				operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				assert_comparison(variable, !=, NULL);
				assert_comparison(operand_node, !=, NULL);
				
				if (ARROW(operand_node, value).f64_value == 0.0) {
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fldz();
				} else if (ARROW(operand_node, value).f64_value == 1.0) {
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fld1();
				} else {
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(*(u64*)ARROW(operand_node, address_in_memory));
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fld_deref_register_as_qword(REG_R11);
					
					if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
						ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable);
					}
				}
				
				break;
			}
			case MLI_OPCODE_MUL_FVAR_FVAL: {
				Variable* variable;
				OperandNode* operand_node;
				
				variable = (Variable*)ARROW(instruction, argument1);
				operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				assert_comparison(variable, !=, NULL);
				assert_comparison(operand_node, !=, NULL);
				
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(*(u64*)ARROW(operand_node, address_in_memory));
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fmul_deref_register_as_qword(REG_R11);
				
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable);
				}
				
				break;
			}
			case MLI_OPCODE_MOV_FVAR_FVAR: {
				Variable* variable1;
				Variable* variable2;
				
				variable1 = (Variable*)ARROW(instruction, argument1);
				variable2 = (Variable*)ARROW(instruction, argument2);
				
				assert_comparison(variable1, !=, NULL);
				assert_comparison(variable2, !=, NULL);
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(stack_offset);
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_sub_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fld_deref_register_as_qword(REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable1);
				}
				
				break;
			}
			case MLI_OPCODE_ADD_FVAR_FVAL: {
				Variable* variable;
				OperandNode* operand_node;
				
				variable = (Variable*)ARROW(instruction, argument1);
				operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				assert_comparison(variable, !=, NULL);
				assert_comparison(operand_node, !=, NULL);
				
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(*(u64*)ARROW(operand_node, address_in_memory));
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fadd_deref_register_as_qword(REG_R11);
				
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable);
				}
				
				break;
			}
			case MLI_OPCODE_ADD_FVAR_FVAR: {
				Variable* variable1;
				Variable* variable2;
				
				variable1 = (Variable*)ARROW(instruction, argument1);
				variable2 = (Variable*)ARROW(instruction, argument2);
				
				assert_comparison(variable1, !=, NULL);
				assert_comparison(variable2, !=, NULL);
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(stack_offset);
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_sub_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fadd_deref_register_as_qword(REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable1);
				}
				
				break;
			}
			case MLI_OPCODE_SUB_FVAR_FVAL: {
				Variable* variable;
				OperandNode* operand_node;
				
				variable = (Variable*)ARROW(instruction, argument1);
				operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				assert_comparison(variable, !=, NULL);
				assert_comparison(operand_node, !=, NULL);
				
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(*(u64*)ARROW(operand_node, address_in_memory));
				ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fsub_deref_register_as_qword(REG_R11);
				
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable);
				}
				
				break;
			}
			case MLI_OPCODE_SUB_FVAR_FVAR: {
				Variable* variable1;
				Variable* variable2;
				
				variable1 = (Variable*)ARROW(instruction, argument1);
				variable2 = (Variable*)ARROW(instruction, argument2);
				
				assert_comparison(variable1, !=, NULL);
				assert_comparison(variable2, !=, NULL);
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_mov_reg_lit64(stack_offset);
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_sub_reg_reg();
					ARROW_ASSIGN(dac, code_length) += dac_get_length_of_fsub_deref_register_as_qword(REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					ARROW_ASSIGN(dac, code_length) += get_length_of_instructions_generated_by_store_variable_on_stack(variable1);
				}
				
				break;
			}
			case MLI_OPCODE_DIV_FVAR_FVAL:
				/* FIXME */
				
				assert_comparison(0, !=, 0);
				break;
			default:
				assert_comparison(ARROW(instruction, opcode), !=, ARROW(instruction, opcode));
		}
	}
	
	ARROW_ASSIGN(dac, code_length) += dac_get_length_of_ending_instructions();
}

attr_nonnull
static
#if DEBUG
	u64
#else
	void
#endif
allocate_memory_for_code_section(DataAndCode* const restrict dac) {
	u64 old_code_length;
	
	assert_comparison(dac, !=, NULL);
	
	old_code_length = ARROW(dac, code_length);
	
	ARROW_ASSIGN(dac, start_of_code) = (char*)m_allocate_writable_and_executable_memory(dac, ARROW(dac, code_length));
	if (unlikely(ARROW(dac, start_of_code) == (void*)-1)) {
		exit(1);
	}
	
	#if DEBUG
		return old_code_length;
	#endif
}

static void put_instructions_into_code_section(
	DataAndCode* const restrict dac,
	MidLevelInstructions* const restrict mli
) {
	u64 i;
	
	dac_append_starting_instructions(dac);
	
	for (i = 0; i < ARROW(mli, used_length); ++i) {
		MidLevelInstruction* const instruction = ARRAY_INDEX(ARROW(mli, mid_level_instructions), i);
		
		switch (ARROW(instruction, opcode)) {
			case MLI_OPCODE_MOV_UVAR_UVAL: {
				Variable* variable = (Variable*)ARROW(instruction, argument1);
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				(void)variable;
				(void)operand_node;
				
				#if DEBUG
					output_nullt_string("mov uresult, ");
					output_u64(ARROW(operand_node, value).u64_value);
					output_newline();
				#endif
				break;
			}
			case MLI_OPCODE_MOV_FVAR_FVAL: {
				Variable* variable = (Variable*)ARROW(instruction, argument1);
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				(void)variable;
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable);
					output_nullt_string(" = ");
					output_f64(ARROW(operand_node, value).f64_value);
					output_newline();
				#endif
				
				if (ARROW(operand_node, value).f64_value == 0.0) {
					dac_append_fldz(dac);
				} else if (ARROW(operand_node, value).f64_value == 1.0) {
					dac_append_fld1(dac);
				} else {
					dac_append_mov_reg_lit64(dac, REG_R11, *(u64*)ARROW(operand_node, address_in_memory));
					dac_append_fld_deref_register_as_qword(dac, REG_R11);
				}
				
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable);
				}
				
				break;
			}
			case MLI_OPCODE_MOV_FVAR_FVAR: {
				Variable* variable1 = (Variable*)ARROW(instruction, argument1);
				Variable* variable2 = (Variable*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable1);
					output_nullt_string(" = ");
					output_variable(variable2);
					output_newline();
				#endif
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					dac_append_mov_reg_reg(dac, REG_R11, REG_RSP);
					dac_append_mov_reg_lit64(dac, REG_R12, stack_offset);
					dac_append_sub_reg_reg(dac, REG_R11, REG_R12);
					dac_append_fld_deref_register_as_qword(dac, REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable1);
				}
				break;
			}
			case MLI_OPCODE_MUL_UVAR_UVAL: {
				/* FIXME */
				assert_comparison(0, !=, 0);
				
				break;
			}
			case MLI_OPCODE_MUL_FVAR_FVAL: {
				Variable* variable = (Variable*)ARROW(instruction, argument1);
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable);
					output_nullt_string(" *= ");
					output_f64(ARROW(operand_node, value).f64_value);
					output_newline();
				#endif
				
				dac_append_mov_reg_lit64(dac, REG_R11, DEREF((u64*)ARROW(operand_node, address_in_memory)));
				dac_append_fmul_deref_register_as_qword(dac, REG_R11);
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable);
				}
				break;
			}
			case MLI_OPCODE_DIV_UVAR_UVAL: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_DIV_FVAR_FVAL: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_ADD_UVAR_UVAL: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_ADD_FVAR_FVAL: {
				Variable* variable = (Variable*)ARROW(instruction, argument1);
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable);
					output_nullt_string(" += ");
					output_f64(ARROW(operand_node, value).f64_value);
					output_newline();
				#endif
				
				dac_append_mov_reg_lit64(dac, REG_R11, DEREF((u64*)ARROW(operand_node, address_in_memory)));
				dac_append_fadd_deref_register_as_qword(dac, REG_R11);
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable);
				}
				
				break;
			}
			case MLI_OPCODE_ADD_UVAR_UVAR: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_ADD_FVAR_FVAR: {
				Variable* variable1 = (Variable*)ARROW(instruction, argument1);
				Variable* variable2 = (Variable*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable1);
					output_nullt_string(" += ");
					output_variable(variable2);
					output_newline();
				#endif
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					dac_append_mov_reg_reg(dac, REG_R11, REG_RSP);
					dac_append_mov_reg_lit64(dac, REG_R12, stack_offset);
					dac_append_sub_reg_reg(dac, REG_R11, REG_R12);
					dac_append_fadd_deref_register_as_qword(dac, REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable1);
				}
				
				break;
			}
			case MLI_OPCODE_SUB_UVAR_UVAL: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_SUB_FVAR_FVAL: {
				Variable* variable = (Variable*)ARROW(instruction, argument1);
				OperandNode* operand_node = (OperandNode*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable);
					output_nullt_string(" -= ");
					output_f64(ARROW(operand_node, value).f64_value);
					output_newline();
				#endif
				
				dac_append_mov_reg_lit64(dac, REG_R11, DEREF((u64*)ARROW(operand_node, address_in_memory)));
				dac_append_fsub_deref_register_as_qword(dac, REG_R11);
				if (instruction == ARROW(variable, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable);
				}
				
				break;
			}
			case MLI_OPCODE_SUB_UVAR_UVAR: {
				assert_comparison(0, !=, 0);
				break;
			}
			case MLI_OPCODE_SUB_FVAR_FVAR: {
				Variable* variable1 = (Variable*)ARROW(instruction, argument1);
				Variable* variable2 = (Variable*)ARROW(instruction, argument2);
				
				#if DEBUG
					output_nullt_string(TAB_STRING "; ");
					output_variable(variable1);
					output_nullt_string(" -= ");
					output_variable(variable2);
					output_newline();
				#endif
				
				{
					const u64 stack_offset = ARROW(variable2, stack_offset);
					
					assert_comparison(stack_offset, !=, 0);
					
					dac_append_mov_reg_reg(dac, REG_R11, REG_RSP);
					dac_append_mov_reg_lit64(dac, REG_R12, stack_offset);
					dac_append_sub_reg_reg(dac, REG_R11, REG_R12);
					dac_append_fsub_deref_register_as_qword(dac, REG_R11);
				}
				
				if (instruction == ARROW(variable1, last_instruction_that_uses_this_variable)) {
					store_variable_on_stack(dac, variable1);
				}
				
				break;
			}
			default:
				assert_comparison(ARROW(instruction, opcode), !=, ARROW(instruction, opcode));
		}
	}
	
	dac_append_ending_instructions(dac);
}

/* Get the length of the instructions generated by store_variable_on_stack() */
static u64 get_length_of_instructions_generated_by_store_variable_on_stack(
	const Variable* const restrict variable
) {
	assert_comparison(variable, !=, NULL);
	
	{
		const u64 stack_offset = ARROW(variable, stack_offset);
		assert_comparison(stack_offset, !=, 0);
		
		return
			dac_get_length_of_mov_reg_reg() +
			dac_get_length_of_mov_reg_lit64(stack_offset) +
			dac_get_length_of_sub_reg_reg() +
			dac_get_length_of_fstp_deref_register_as_qword(REG_R11);
	}
}

static void store_variable_on_stack(DataAndCode* const restrict dac, Variable* const restrict variable) {
	assert_comparison(dac, !=, NULL);
	assert_comparison(variable, !=, NULL);
	
	{
		const u64 stack_offset = ARROW(variable, stack_offset);
		assert_comparison(stack_offset, !=, 0);
		
		output_nullt_string(TAB_STRING "; store st0 on the stack\n");
		
		dac_append_mov_reg_reg(dac, REG_R11, REG_RSP);
		dac_append_mov_reg_lit64(dac, REG_R12, stack_offset);
		dac_append_sub_reg_reg(dac, REG_R11, REG_R12);
		dac_append_fstp_deref_register_as_qword(dac, REG_R11);
		output_newline();
	}
}