/******************************************************************************
 * Copyright (C) 2020-2021 Dibyendu Majumdar
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 ******************************************************************************/

#ifndef ravicomp_LINEARIZER_H
#define ravicomp_LINEARIZER_H

#include "ravi_compiler.h"

#include "common.h"
#include "parser.h"
#include "allocate.h"
#include "membuf.h"
#include "ptrlist.h"

/*
Linearizer component is responsible for translating the abstract syntax tree to
a Linear intermediate representation (IR).
*/
typedef struct Instruction Instruction;
typedef struct BasicBlock BasicBlock;
typedef struct Proc Proc;
typedef struct Constant Constant;
typedef struct Graph Graph;

DECLARE_PTR_LIST(InstructionList, Instruction);
DECLARE_PTR_LIST(PseudoList, Pseudo);
DECLARE_PTR_LIST(ProcList, Proc);

#define container_of(ptr, type, member) ((type *)((char *)(ptr)-offsetof(type, member)))

/* order is important here ! */
enum opcode {
	op_nop,
	op_ret,
	op_add,
	op_addff,
	op_addfi,
	op_addii,
	op_sub,
	op_subff,
	op_subfi,
	op_subif,
	op_subii,
	op_mul,
	op_mulff,
	op_mulfi,
	op_mulii,
	op_div,
	op_divff,
	op_divfi,
	op_divif,
	op_divii,
	op_idiv,
	op_band,
	op_bandii,
	op_bor,
	op_borii,
	op_bxor,
	op_bxorii,
	op_shl,
	op_shlii,
	op_shr,
	op_shrii,
	op_eq,
	op_eqii,
	op_eqff,
	op_lt,
	op_ltii,
	op_ltff,
	op_le,
	op_leii,
	op_leff,
	op_mod,
	op_pow,
	op_closure,
	op_unm,
	op_unmi,
	op_unmf,
	op_len,
	op_leni,
	op_toint,
	op_toflt,
	op_toclosure,
	op_tostring,
	op_toiarray,
	op_tofarray,
	op_totable,
	op_totype,
	op_not,
	op_bnot,
	op_loadglobal,
	op_newtable,
	op_newiarray,
	op_newfarray,
	op_put, /* target is any */
	op_put_ikey,
	op_put_skey,
	op_tput, /* target is table */
	op_tput_ikey,
	op_tput_skey,
	op_iaput, /* target is integer[]*/
	op_iaput_ival,
	op_faput, /* target is number[] */
	op_faput_fval,
	op_cbr,
	op_br,
	op_mov,
	op_movi,
	op_movif, /* int to float if compatible else error */
	op_movf,
	op_movfi, /* float to int if compatible else error */
	op_call,
	op_get,
	op_get_ikey,
	op_get_skey,
	op_tget,
	op_tget_ikey,
	op_tget_skey,
	op_iaget,
	op_iaget_ikey,
	op_faget,
	op_faget_ikey,
	op_storeglobal,
	op_close,
	op_string_concat
};

/*
* The IR instructions use operands and targets of type pseudo, which
* is a way of referencing several different types of objects.
*/
enum PseudoType {
	PSEUDO_SYMBOL, /* An object of type lua_symbol representing local var or upvalue, always refers to Lua stack relative to 'base' */
	PSEUDO_TEMP_FLT, /* A floating point temp - may also be used for locals that don't escape - refers to C var */
	PSEUDO_TEMP_INT, /* An integer temp - may also be used for locals that don't escape - refers to C var */
	PSEUDO_TEMP_BOOL, /* An integer temp but restricted to 1 and 0  - refers to C var, shares the virtual C stack with PSEUDO_TEMP_INT */
	PSEUDO_TEMP_ANY, /* A temp of any type - will always be on Lua stack relative to 'base' */
	PSEUDO_CONSTANT, /* A literal value */
	PSEUDO_PROC, /* A proc / function */
	PSEUDO_NIL, /* Literal */
	PSEUDO_TRUE, /* Literal */
	PSEUDO_FALSE, /* Literal */
	PSEUDO_BLOCK, /* Points to a basic block, used as targets for jumps */
	PSEUDO_RANGE, /* Represents a range of registers from a certain starting register on Lua stack relative to 'base' */
	PSEUDO_RANGE_SELECT, /* Picks a certain register from a range, resolves to register on Lua stack, relative to 'base' */
	/* TODO we need a type for var args */
	PSEUDO_LUASTACK /* Specifies a Lua stack position - not used by linearizer - for use by codegen. This is relative to CI->func rather than 'base' */
};

/* pseudo represents a pseudo (virtual) register */
struct Pseudo {
	unsigned type : 4, regnum : 16, freed : 1;
	Instruction *insn; /* instruction that created this pseudo */
	union {
		LuaSymbol *symbol;	 /* PSEUDO_SYMBOL */
		const Constant *constant; /* PSEUDO_CONSTANT */
		LuaSymbol *temp_for_local; /* PSEUDO_TEMP - if the temp represents a local */
		Proc *proc;		 /* PSEUDO_PROC */
		BasicBlock *block;	 /* PSEUDO_BLOCK */
		Pseudo *range_pseudo;	 /* PSEUDO_RANGE_SELECT */
		int stackidx; /* PSEUDO_LUASTACK */
	};
};

/* single instruction */
struct Instruction {
	unsigned opcode : 8;
	PseudoList *operands;
	PseudoList *targets;
	BasicBlock *block; /* owning block */
};

/* Basic block */
struct BasicBlock {
	nodeId_t index; /* The index of the block is a key to enable retrieving the block from its container */
	InstructionList *insns; /* Note that if number of instructions is 0 then the block was logically deleted */
};
DECLARE_PTR_LIST(BasicBlockList, BasicBlock);

typedef struct PseudoGenerator {
	uint8_t next_reg; /* Next register if no free registers, initially 0 */
	int16_t free_pos; /* number of values in free_regs */
	uint8_t free_regs[256]; /* list of free registers */
} PseudoGenerator;

struct Constant {
	uint8_t type;	/* ravitype_t RAVI_TNUMINT, RAVI_TNUMFLT or RAVI_TSTRING */
	uint16_t index; /* index number starting from 0 assigned to each constant - acts like a reg num.
			 * Each type will be assigned separate range */
	union {
		lua_Integer i;
		lua_Number n;
		const StringObject *s;
	};
};

/* proc is a type of cfg */
struct Proc {
	unsigned node_count;
	unsigned allocated;
	BasicBlock **nodes;
	uint32_t id; /* ID for the proc */
	LinearizerState *linearizer;
	ProcList *procs;	/* procs defined in this proc */
	Proc *parent;		/* enclosing proc */
	AstNode *function_expr; /* function ast that we are compiling */
	Scope *current_scope;
	BasicBlock *current_bb;
	BasicBlock *current_break_target; /* track the current break target, previous target must be saved /
						     restored in stack discipline */
	Scope *current_break_scope;  /* as above track the block scope */
	PseudoGenerator local_pseudos;	  /* locals */
	PseudoGenerator temp_int_pseudos; /* temporaries known to be integer type */
	PseudoGenerator temp_flt_pseudos; /* temporaries known to be number type */
	PseudoGenerator temp_pseudos;	  /* All other temporaries */
	Set *constants;			  /* constants used by this proc */
	uint16_t num_intconstants;
	uint16_t num_fltconstants;
	uint16_t num_strconstants;
	Graph *cfg;  /* place holder for control flow graph; the linearizer does not create this */
	char funcname[30]; /* Each proc needs a name inside a module - name is a short string */
	void *userdata; /* For use by code generator */
};

struct LinearizerState {
	Allocator instruction_allocator;
	Allocator pseudo_allocator;
	Allocator ptrlist_allocator;
	Allocator basic_block_allocator;
	Allocator proc_allocator;
	Allocator unsized_allocator;
	Allocator constant_allocator;
	CompilerState *ast_container;
	Proc *main_proc;	     /* The root of the compiled chunk of code */
	ProcList *all_procs; /* All procs allocated by the linearizer */
	Proc *current_proc;   /* proc being compiled */
	uint32_t proc_id;
};

void raviX_show_linearizer(LinearizerState *linearizer, TextBuffer *mb);
void raviX_output_basic_block_as_table(Proc *proc, BasicBlock *bb, TextBuffer *mb);

Instruction *raviX_last_instruction(BasicBlock *block);
Pseudo* raviX_allocate_stack_pseudo(Proc* proc, unsigned reg);
const char *raviX_opcode_name(unsigned int opcode);

#endif