%% -*- erlang-indent-level: 2 -*-

-module(hipe_llvm).

-export([
    mk_ret/1,
    ret_ret_list/1,

    mk_br/1,
    br_dst/1,

    mk_br_cond/3,
    mk_br_cond/4,
    br_cond_cond/1,
    br_cond_true_label/1,
    br_cond_false_label/1,
    br_cond_meta/1,

    mk_indirectbr/3,
    indirectbr_type/1,
    indirectbr_address/1,
    indirectbr_label_list/1,

    mk_switch/4,
    switch_type/1,
    switch_value/1,
    switch_default_label/1,
    switch_value_label_list/1,

    mk_invoke/9,
    invoke_dst/1,
    invoke_cconv/1,
    invoke_ret_attrs/1,
    invoke_type/1,
    invoke_fnptrval/1,
    invoke_arglist/1,
    invoke_fn_attrs/1,
    invoke_to_label/1,
    invoke_unwind_label/1,

    mk_operation/6,
    operation_dst/1,
    operation_op/1,
    operation_type/1,
    operation_src1/1,
    operation_src2/1,
    operation_options/1,

    mk_extractvalue/5,
    extractvalue_dst/1,
    extractvalue_type/1,
    extractvalue_val/1,
    extractvalue_idx/1,
    extractvalue_idxs/1,

    mk_insertvalue/7,
    insertvalue_dst/1,
    insertvalue_val_type/1,
    insertvalue_val/1,
    insertvalue_elem_type/1,
    insertvalue_elem/1,
    insertvalue_idx/1,
    insertvalue_idxs/1,

    mk_alloca/4,
    alloca_dst/1,
    alloca_type/1,
    alloca_num/1,
    alloca_align/1,

    mk_load/6,
    load_dst/1,
    load_p_type/1,
    load_pointer/1,
    load_alignment/1,
    load_nontemporal/1,
    load_volatile/1,

    mk_store/7,
    store_type/1,
    store_value/1,
    store_p_type/1,
    store_pointer/1,
    store_alignment/1,
    store_nontemporal/1,
    store_volatile/1,

    mk_getelementptr/5,
    getelementptr_dst/1,
    getelementptr_p_type/1,
    getelementptr_value/1,
    getelementptr_typed_idxs/1,
    getelementptr_inbounds/1,

    mk_conversion/5,
    conversion_dst/1,
    conversion_op/1,
    conversion_src_type/1,
    conversion_src/1,
    conversion_dst_type/1,

    mk_sitofp/4,
    sitofp_dst/1,
    sitofp_src_type/1,
    sitofp_src/1,
    sitofp_dst_type/1,

    mk_ptrtoint/4,
    ptrtoint_dst/1,
    ptrtoint_src_type/1,
    ptrtoint_src/1,
    ptrtoint_dst_type/1,

    mk_inttoptr/4,
    inttoptr_dst/1,
    inttoptr_src_type/1,
    inttoptr_src/1,
    inttoptr_dst_type/1,

    mk_icmp/5,
    icmp_dst/1,
    icmp_cond/1,
    icmp_type/1,
    icmp_src1/1,
    icmp_src2/1,

    mk_fcmp/5,
    fcmp_dst/1,
    fcmp_cond/1,
    fcmp_type/1,
    fcmp_src1/1,
    fcmp_src2/1,

    mk_phi/3,
    phi_dst/1,
    phi_type/1,
    phi_value_label_list/1,

    mk_select/6,
    select_dst/1,
    select_cond/1,
    select_typ1/1,
    select_val1/1,
    select_typ2/1,
    select_val2/1,

    mk_call/8,
    call_dst/1,
    call_is_tail/1,
    call_cconv/1,
    call_ret_attrs/1,
    call_type/1,
    call_fnptrval/1,
    call_arglist/1,
    call_fn_attrs/1,

    mk_fun_def/10,
    fun_def_linkage/1,
    fun_def_visibility/1,
    fun_def_cconv/1,
    fun_def_ret_attrs/1,
    fun_def_type/1,
    fun_def_name/1,
    fun_def_arglist/1,
    fun_def_fn_attrs/1,
    fun_def_align/1,
    fun_def_body/1,

    mk_fun_decl/8,
    fun_decl_linkage/1,
    fun_decl_visibility/1,
    fun_decl_cconv/1,
    fun_decl_ret_attrs/1,
    fun_decl_type/1,
    fun_decl_name/1,
    fun_decl_arglist/1,
    fun_decl_align/1,

    mk_landingpad/0,

    mk_comment/1,
    comment_text/1,

    mk_label/1,
    label_label/1,
    is_label/1,

    mk_const_decl/4,
    const_decl_dst/1,
    const_decl_decl_type/1,
    const_decl_type/1,
    const_decl_value/1,

    mk_asm/1,
    asm_instruction/1,

    mk_adj_stack/3,
    adj_stack_offset/1,
    adj_stack_register/1,
    adj_stack_type/1,

    mk_meta/2,
    meta_id/1,
    meta_operands/1
  ]).

-export([
    mk_void/0,

    mk_label_type/0,

    mk_int/1,
    int_width/1,

    mk_double/0,

    mk_pointer/1,
    pointer_type/1,

    mk_array/2,
    array_size/1,
    array_type/1,

    mk_vector/2,
    vector_size/1,
    vector_type/1,

    mk_struct/1,
    struct_type_list/1,

    mk_fun/2,
    function_ret_type/1,
    function_arg_type_list/1
  ]).

-export([pp_ins_list/3, pp_ins/3]).


%%-----------------------------------------------------------------------------
%% Abstract Data Types for LLVM Assembly.
%%-----------------------------------------------------------------------------

%% Terminator Instructions
-record(llvm_ret, {ret_list=[]}).
-type llvm_ret() :: #llvm_ret{}.

-record(llvm_br, {dst}).
-type llvm_br() :: #llvm_br{}.

-record(llvm_br_cond, {'cond', true_label, false_label, meta=[]}).
-type llvm_br_cond() :: #llvm_br_cond{}.

-record(llvm_indirectbr, {type, address, label_list}).
-type llvm_indirectbr() :: #llvm_indirectbr{}.

-record(llvm_switch, {type, value, default_label, value_label_list=[]}).
-type llvm_switch() :: #llvm_switch{}.

-record(llvm_invoke, {dst, cconv=[], ret_attrs=[], type, fnptrval, arglist=[],
                      fn_attrs=[], to_label, unwind_label}).
-type llvm_invoke() :: #llvm_invoke{}.

%% Binary Operations
-record(llvm_operation, {dst, op, type, src1, src2, options=[]}).
-type llvm_operation() :: #llvm_operation{}.

%% Aggregate Operations
-record(llvm_extractvalue, {dst, type, val, idx, idxs=[]}).
-type llvm_extractvalue() :: #llvm_extractvalue{}.

-record(llvm_insertvalue, {dst, val_type, val, elem_type, elem, idx, idxs=[]}).
-type llvm_insertvalue() :: #llvm_insertvalue{}.

%% Memory Access and Addressing Operations
-record(llvm_alloca, {dst, type, num=[], align=[]}).
-type llvm_alloca() :: #llvm_alloca{}.

-record(llvm_load, {dst, p_type, pointer, alignment=[], nontemporal=[],
                    volatile=false}).
-type llvm_load() :: #llvm_load{}.

-record(llvm_store, {type, value, p_type, pointer, alignment=[],
                     nontemporal=[], volatile=false}).
-type llvm_store() :: #llvm_store{}.

-record(llvm_getelementptr, {dst, p_type, value, typed_idxs, inbounds}).
-type llvm_getelementptr() :: #llvm_getelementptr{}.

%% Conversion Operations
-record(llvm_conversion, {dst, op, src_type, src, dst_type}).
-type llvm_conversion() :: #llvm_conversion{}.

-record(llvm_sitofp, {dst, src_type, src, dst_type}).
-type llvm_sitofp() :: #llvm_sitofp{}.

-record(llvm_ptrtoint, {dst, src_type, src, dst_type}).
-type llvm_ptrtoint() :: #llvm_ptrtoint{}.

-record(llvm_inttoptr, {dst, src_type, src, dst_type}).
-type llvm_inttoptr() :: #llvm_inttoptr{}.

%% Other Operations
-record(llvm_icmp, {dst, 'cond', type, src1, src2}).
-type llvm_icmp() :: #llvm_icmp{}.

-record(llvm_fcmp, {dst, 'cond', type, src1, src2}).
-type llvm_fcmp() :: #llvm_fcmp{}.

-record(llvm_phi, {dst, type, value_label_list}).
-type llvm_phi() :: #llvm_phi{}.

-record(llvm_select, {dst, 'cond', typ1, val1, typ2, val2}).
-type llvm_select() :: #llvm_select{}.

-record(llvm_call, {dst=[], is_tail = false, cconv = [], ret_attrs = [], type,
                    fnptrval, arglist = [], fn_attrs = []}).
-type llvm_call() :: #llvm_call{}.

-record(llvm_fun_def, {linkage=[], visibility=[], cconv=[], ret_attrs=[],
    type, 'name', arglist=[], fn_attrs=[], align=[], body=[]}).
-type llvm_fun_def() :: #llvm_fun_def{}.

-record(llvm_fun_decl, {linkage=[], visibility=[], cconv=[], ret_attrs=[],
    type, 'name', arglist=[],  align=[]}).
-type llvm_fun_decl() :: #llvm_fun_decl{}.

-record(llvm_landingpad, {}).
-type llvm_landingpad() :: #llvm_landingpad{}.

-record(llvm_comment, {text}).
-type llvm_comment() :: #llvm_comment{}.

-record(llvm_label, {label}).
-type llvm_label() :: #llvm_label{}.

-record(llvm_const_decl, {dst, decl_type, type, value}).
-type llvm_const_decl() :: #llvm_const_decl{}.

-record(llvm_asm, {instruction}).
-type llvm_asm() :: #llvm_asm{}.

-record(llvm_adj_stack, {offset, 'register', type}).
-type llvm_adj_stack() :: #llvm_adj_stack{}.

-record(llvm_meta, {id :: string(),
		    operands :: [string() | integer() | llvm_meta()]}).
-type llvm_meta() :: #llvm_meta{}.

%% A type for any LLVM instruction
-type llvm_instr() :: llvm_ret() | llvm_br() | llvm_br_cond()
                    | llvm_indirectbr() | llvm_switch() | llvm_invoke()
                    | llvm_operation() | llvm_extractvalue()
                    | llvm_insertvalue() | llvm_alloca() | llvm_load()
                    | llvm_store() | llvm_getelementptr() | llvm_conversion()
                    | llvm_sitofp() | llvm_ptrtoint() | llvm_inttoptr()
                    | llvm_icmp() | llvm_fcmp() | llvm_phi() | llvm_select()
                    | llvm_call() | llvm_fun_def() | llvm_fun_decl()
                    | llvm_landingpad() | llvm_comment() | llvm_label()
                    | llvm_const_decl() | llvm_asm() | llvm_adj_stack()
                    | llvm_meta().

%% Types
-record(llvm_void, {}).
%-type llvm_void() :: #llvm_void{}.

-record(llvm_label_type, {}).
%-type llvm_label_type() :: #llvm_label_type{}.

-record(llvm_int, {width}).
%-type llvm_int() :: #llvm_int{}.

-record(llvm_float, {}).
%-type llvm_float() :: #llvm_float{}.

-record(llvm_double, {}).
%-type llvm_double() :: #llvm_double{}.

-record(llvm_fp80, {}).
%-type llvm_fp80() :: #llvm_fp80{}.

-record(llvm_fp128, {}).
%-type llvm_fp128() :: #llvm_fp128{}.

-record(llvm_ppc_fp128, {}).
%-type llvm_ppc_fp128() :: #llvm_ppc_fp128{}.

-record(llvm_pointer, {type}).
%-type llvm_pointer() :: #llvm_pointer{}.

-record(llvm_vector, {'size', type}).
%-type llvm_vector() :: #llvm_vector{}.

-record(llvm_struct, {type_list}).
%-type llvm_struct() :: #llvm_struct{}.

-record(llvm_array, {'size', type}).
%-type llvm_array() :: #llvm_array{}.

-record(llvm_fun, {ret_type, arg_type_list}).
%-type llvm_fun() :: #llvm_fun{}.

%%-----------------------------------------------------------------------------
%% Accessor Functions
%%-----------------------------------------------------------------------------

%% ret
mk_ret(Ret_list) -> #llvm_ret{ret_list=Ret_list}.
ret_ret_list(#llvm_ret{ret_list=Ret_list}) -> Ret_list.

%% br
mk_br(Dst) -> #llvm_br{dst=Dst}.
br_dst(#llvm_br{dst=Dst}) -> Dst.

%% br_cond
mk_br_cond(Cond, True_label, False_label) ->
  #llvm_br_cond{'cond'=Cond, true_label=True_label, false_label=False_label}.
mk_br_cond(Cond, True_label, False_label, Metadata) ->
  #llvm_br_cond{'cond'=Cond, true_label=True_label, false_label=False_label,
		meta=Metadata}.
br_cond_cond(#llvm_br_cond{'cond'=Cond}) -> Cond.
br_cond_true_label(#llvm_br_cond{true_label=True_label}) -> True_label.
br_cond_false_label(#llvm_br_cond{false_label=False_label}) ->
  False_label.
br_cond_meta(#llvm_br_cond{meta=Metadata}) -> Metadata.

%% indirectbr
mk_indirectbr(Type, Address, Label_list) -> #llvm_indirectbr{type=Type, address=Address, label_list=Label_list}.
indirectbr_type(#llvm_indirectbr{type=Type}) -> Type.
indirectbr_address(#llvm_indirectbr{address=Address}) -> Address.
indirectbr_label_list(#llvm_indirectbr{label_list=Label_list}) -> Label_list.

%% invoke
mk_invoke(Dst, Cconv, Ret_attrs, Type, Fnptrval, Arglist, Fn_attrs, To_label, Unwind_label) ->
  #llvm_invoke{dst=Dst, cconv=Cconv, ret_attrs=Ret_attrs, type=Type,
    fnptrval=Fnptrval, arglist=Arglist, fn_attrs=Fn_attrs, to_label=To_label,
              unwind_label=Unwind_label}.
invoke_dst(#llvm_invoke{dst=Dst}) -> Dst.
invoke_cconv(#llvm_invoke{cconv=Cconv}) -> Cconv.
invoke_ret_attrs(#llvm_invoke{ret_attrs=Ret_attrs}) -> Ret_attrs.
invoke_type(#llvm_invoke{type=Type}) -> Type.
invoke_fnptrval(#llvm_invoke{fnptrval=Fnptrval}) -> Fnptrval.
invoke_arglist(#llvm_invoke{arglist=Arglist}) -> Arglist.
invoke_fn_attrs(#llvm_invoke{fn_attrs=Fn_attrs}) -> Fn_attrs.
invoke_to_label(#llvm_invoke{to_label=To_label}) -> To_label.
invoke_unwind_label(#llvm_invoke{unwind_label=Unwind_label}) -> Unwind_label.

%% switch
mk_switch(Type, Value, Default_label, Value_label_list) ->
  #llvm_switch{type=Type, value=Value, default_label=Default_label,
              value_label_list=Value_label_list}.
switch_type(#llvm_switch{type=Type}) -> Type.
switch_value(#llvm_switch{value=Value}) -> Value.
switch_default_label(#llvm_switch{default_label=Default_label}) ->
  Default_label.
switch_value_label_list(#llvm_switch{value_label_list=Value_label_list}) ->
  Value_label_list.

%% operation
mk_operation(Dst, Op, Type, Src1, Src2, Options) ->
  #llvm_operation{dst=Dst, op=Op, type=Type, src1=Src1, src2=Src2,
    options=Options}.
operation_dst(#llvm_operation{dst=Dst}) -> Dst.
operation_op(#llvm_operation{op=Op}) -> Op.
operation_type(#llvm_operation{type=Type}) -> Type.
operation_src1(#llvm_operation{src1=Src1}) -> Src1.
operation_src2(#llvm_operation{src2=Src2}) -> Src2.
operation_options(#llvm_operation{options=Options}) -> Options.

%% extractvalue
mk_extractvalue(Dst, Type, Val, Idx, Idxs) ->
  #llvm_extractvalue{dst=Dst,type=Type,val=Val,idx=Idx,idxs=Idxs}.
extractvalue_dst(#llvm_extractvalue{dst=Dst}) -> Dst.
extractvalue_type(#llvm_extractvalue{type=Type}) -> Type.
extractvalue_val(#llvm_extractvalue{val=Val}) -> Val.
extractvalue_idx(#llvm_extractvalue{idx=Idx}) -> Idx.
extractvalue_idxs(#llvm_extractvalue{idxs=Idxs}) -> Idxs.

%% insertvalue
mk_insertvalue(Dst, Val_type, Val, Elem_type, Elem, Idx, Idxs) ->
  #llvm_insertvalue{dst=Dst, val_type=Val_type, val=Val, elem_type=Elem_type,
                    elem=Elem, idx=Idx, idxs=Idxs}.
insertvalue_dst(#llvm_insertvalue{dst=Dst}) -> Dst.
insertvalue_val_type(#llvm_insertvalue{val_type=Val_type}) -> Val_type.
insertvalue_val(#llvm_insertvalue{val=Val}) -> Val.
insertvalue_elem_type(#llvm_insertvalue{elem_type=Elem_type}) -> Elem_type.
insertvalue_elem(#llvm_insertvalue{elem=Elem}) -> Elem.
insertvalue_idx(#llvm_insertvalue{idx=Idx}) -> Idx.
insertvalue_idxs(#llvm_insertvalue{idxs=Idxs}) -> Idxs.

%% alloca
mk_alloca(Dst, Type, Num, Align) ->
  #llvm_alloca{dst=Dst, type=Type, num=Num, align=Align}.
alloca_dst(#llvm_alloca{dst=Dst}) -> Dst.
alloca_type(#llvm_alloca{type=Type}) -> Type.
alloca_num(#llvm_alloca{num=Num}) -> Num.
alloca_align(#llvm_alloca{align=Align}) -> Align.

%% load
mk_load(Dst, Type, Pointer, Alignment, Nontemporal, Volatile) ->
  #llvm_load{dst=Dst, p_type=Type, pointer=Pointer, alignment=Alignment,
    nontemporal=Nontemporal, volatile=Volatile}.
load_dst(#llvm_load{dst=Dst}) -> Dst.
load_p_type(#llvm_load{p_type=Type}) -> Type.
load_pointer(#llvm_load{pointer=Pointer}) -> Pointer.
load_alignment(#llvm_load{alignment=Alignment}) -> Alignment.
load_nontemporal(#llvm_load{nontemporal=Nontemporal}) -> Nontemporal.
load_volatile(#llvm_load{volatile=Volatile}) -> Volatile.

%% store
mk_store(Type, Value, P_Type, Pointer, Alignment, Nontemporal, Volatile) ->
  #llvm_store{type=Type, value=Value, p_type=P_Type, pointer=Pointer, alignment=Alignment,
    nontemporal=Nontemporal, volatile=Volatile}.
store_type(#llvm_store{type=Type}) -> Type.
store_value(#llvm_store{value=Value}) -> Value.
store_p_type(#llvm_store{p_type=P_Type}) -> P_Type.
store_pointer(#llvm_store{pointer=Pointer}) -> Pointer.
store_alignment(#llvm_store{alignment=Alignment}) -> Alignment.
store_nontemporal(#llvm_store{nontemporal=Nontemporal}) -> Nontemporal.
store_volatile(#llvm_store{volatile=Volatile}) -> Volatile.

%% getelementptr
mk_getelementptr(Dst, P_Type, Value, Typed_Idxs, Inbounds) ->
  #llvm_getelementptr{dst=Dst,p_type=P_Type, value=Value,
                      typed_idxs=Typed_Idxs, inbounds=Inbounds}.
getelementptr_dst(#llvm_getelementptr{dst=Dst}) -> Dst.
getelementptr_p_type(#llvm_getelementptr{p_type=P_Type}) -> P_Type.
getelementptr_value(#llvm_getelementptr{value=Value}) -> Value.
getelementptr_typed_idxs(#llvm_getelementptr{typed_idxs=Typed_Idxs}) -> Typed_Idxs.
getelementptr_inbounds(#llvm_getelementptr{inbounds=Inbounds}) -> Inbounds.

%% conversion
mk_conversion(Dst, Op, Src_type, Src, Dst_type) ->
  #llvm_conversion{dst=Dst, op=Op, src_type=Src_type, src=Src, dst_type=Dst_type}.
conversion_dst(#llvm_conversion{dst=Dst}) -> Dst.
conversion_op(#llvm_conversion{op=Op}) -> Op.
conversion_src_type(#llvm_conversion{src_type=Src_type}) -> Src_type.
conversion_src(#llvm_conversion{src=Src}) -> Src.
conversion_dst_type(#llvm_conversion{dst_type=Dst_type}) -> Dst_type.

%% sitofp
mk_sitofp(Dst, Src_type, Src, Dst_type) ->
  #llvm_sitofp{dst=Dst, src_type=Src_type, src=Src, dst_type=Dst_type}.
sitofp_dst(#llvm_sitofp{dst=Dst}) -> Dst.
sitofp_src_type(#llvm_sitofp{src_type=Src_type}) -> Src_type.
sitofp_src(#llvm_sitofp{src=Src}) -> Src.
sitofp_dst_type(#llvm_sitofp{dst_type=Dst_type}) -> Dst_type.

%% ptrtoint
mk_ptrtoint(Dst, Src_Type, Src, Dst_Type) ->
  #llvm_ptrtoint{dst=Dst, src_type=Src_Type, src=Src, dst_type=Dst_Type}.
ptrtoint_dst(#llvm_ptrtoint{dst=Dst}) -> Dst.
ptrtoint_src_type(#llvm_ptrtoint{src_type=Src_Type}) -> Src_Type.
ptrtoint_src(#llvm_ptrtoint{src=Src}) -> Src.
ptrtoint_dst_type(#llvm_ptrtoint{dst_type=Dst_Type}) -> Dst_Type .

%% inttoptr
mk_inttoptr(Dst, Src_Type, Src, Dst_Type) ->
  #llvm_inttoptr{dst=Dst, src_type=Src_Type, src=Src, dst_type=Dst_Type}.
inttoptr_dst(#llvm_inttoptr{dst=Dst}) -> Dst.
inttoptr_src_type(#llvm_inttoptr{src_type=Src_Type}) -> Src_Type.
inttoptr_src(#llvm_inttoptr{src=Src}) -> Src.
inttoptr_dst_type(#llvm_inttoptr{dst_type=Dst_Type}) -> Dst_Type .

%% icmp
mk_icmp(Dst, Cond, Type, Src1, Src2) ->
  #llvm_icmp{dst=Dst,'cond'=Cond,type=Type,src1=Src1,src2=Src2}.
icmp_dst(#llvm_icmp{dst=Dst}) -> Dst.
icmp_cond(#llvm_icmp{'cond'=Cond}) -> Cond.
icmp_type(#llvm_icmp{type=Type}) -> Type.
icmp_src1(#llvm_icmp{src1=Src1}) -> Src1.
icmp_src2(#llvm_icmp{src2=Src2}) -> Src2.

%% fcmp
mk_fcmp(Dst, Cond, Type, Src1, Src2) ->
  #llvm_fcmp{dst=Dst,'cond'=Cond,type=Type,src1=Src1,src2=Src2}.
fcmp_dst(#llvm_fcmp{dst=Dst}) -> Dst.
fcmp_cond(#llvm_fcmp{'cond'=Cond}) -> Cond.
fcmp_type(#llvm_fcmp{type=Type}) -> Type.
fcmp_src1(#llvm_fcmp{src1=Src1}) -> Src1.
fcmp_src2(#llvm_fcmp{src2=Src2}) -> Src2.

%% phi
mk_phi(Dst, Type, Value_label_list) ->
  #llvm_phi{dst=Dst, type=Type,value_label_list=Value_label_list}.
phi_dst(#llvm_phi{dst=Dst}) -> Dst.
phi_type(#llvm_phi{type=Type}) -> Type.
phi_value_label_list(#llvm_phi{value_label_list=Value_label_list}) ->
  Value_label_list.

%% select
mk_select(Dst, Cond, Typ1, Val1, Typ2, Val2) ->
  #llvm_select{dst=Dst, 'cond'=Cond, typ1=Typ1, val1=Val1, typ2=Typ2, val2=Val2}.
select_dst(#llvm_select{dst=Dst}) -> Dst.
select_cond(#llvm_select{'cond'=Cond}) -> Cond.
select_typ1(#llvm_select{typ1=Typ1}) -> Typ1.
select_val1(#llvm_select{val1=Val1}) -> Val1.
select_typ2(#llvm_select{typ2=Typ2}) -> Typ2.
select_val2(#llvm_select{val2=Val2}) -> Val2.

%% call
mk_call(Dst, Is_tail, Cconv, Ret_attrs, Type, Fnptrval, Arglist, Fn_attrs) ->
  #llvm_call{dst=Dst, is_tail=Is_tail, cconv=Cconv, ret_attrs=Ret_attrs,
    type=Type, fnptrval=Fnptrval, arglist=Arglist, fn_attrs=Fn_attrs}.
call_dst(#llvm_call{dst=Dst}) -> Dst.
call_is_tail(#llvm_call{is_tail=Is_tail}) -> Is_tail.
call_cconv(#llvm_call{cconv=Cconv}) -> Cconv.
call_ret_attrs(#llvm_call{ret_attrs=Ret_attrs}) -> Ret_attrs.
call_type(#llvm_call{type=Type}) -> Type.
call_fnptrval(#llvm_call{fnptrval=Fnptrval}) -> Fnptrval.
call_arglist(#llvm_call{arglist=Arglist}) -> Arglist.
call_fn_attrs(#llvm_call{fn_attrs=Fn_attrs}) -> Fn_attrs.

%% fun_def
mk_fun_def(Linkage, Visibility, Cconv, Ret_attrs, Type, Name, Arglist,
           Fn_attrs, Align, Body) ->
  #llvm_fun_def{
    linkage=Linkage,
    visibility=Visibility,
    cconv=Cconv,
    ret_attrs=Ret_attrs,
    type=Type,
    'name'=Name,
    arglist=Arglist,
    fn_attrs=Fn_attrs,
    align=Align,
    body=Body
  }.

fun_def_linkage(#llvm_fun_def{linkage=Linkage}) -> Linkage.
fun_def_visibility(#llvm_fun_def{visibility=Visibility}) -> Visibility.
fun_def_cconv(#llvm_fun_def{cconv=Cconv}) -> Cconv .
fun_def_ret_attrs(#llvm_fun_def{ret_attrs=Ret_attrs}) -> Ret_attrs.
fun_def_type(#llvm_fun_def{type=Type}) -> Type.
fun_def_name(#llvm_fun_def{'name'=Name}) -> Name.
fun_def_arglist(#llvm_fun_def{arglist=Arglist}) -> Arglist.
fun_def_fn_attrs(#llvm_fun_def{fn_attrs=Fn_attrs}) -> Fn_attrs.
fun_def_align(#llvm_fun_def{align=Align}) -> Align.
fun_def_body(#llvm_fun_def{body=Body}) -> Body.

%% fun_decl
mk_fun_decl(Linkage, Visibility, Cconv, Ret_attrs, Type, Name, Arglist, Align)->
  #llvm_fun_decl{
    linkage=Linkage,
    visibility=Visibility,
    cconv=Cconv,
    ret_attrs=Ret_attrs,
    type=Type,
    'name'=Name,
    arglist=Arglist,
    align=Align
  }.

fun_decl_linkage(#llvm_fun_decl{linkage=Linkage}) -> Linkage.
fun_decl_visibility(#llvm_fun_decl{visibility=Visibility}) -> Visibility.
fun_decl_cconv(#llvm_fun_decl{cconv=Cconv}) -> Cconv .
fun_decl_ret_attrs(#llvm_fun_decl{ret_attrs=Ret_attrs}) -> Ret_attrs.
fun_decl_type(#llvm_fun_decl{type=Type}) -> Type.
fun_decl_name(#llvm_fun_decl{'name'=Name}) -> Name.
fun_decl_arglist(#llvm_fun_decl{arglist=Arglist}) -> Arglist.
fun_decl_align(#llvm_fun_decl{align=Align}) -> Align.

%% landingpad
mk_landingpad() -> #llvm_landingpad{}.

%% comment
mk_comment(Text) -> #llvm_comment{text=Text}.
comment_text(#llvm_comment{text=Text}) -> Text.

%% label
mk_label(Label) -> #llvm_label{label=Label}.
label_label(#llvm_label{label=Label}) -> Label.

-spec is_label(llvm_instr()) -> boolean().
is_label(#llvm_label{}) -> true;
is_label(#llvm_ret{}) -> false;
is_label(#llvm_br{}) -> false;
is_label(#llvm_br_cond{}) -> false;
is_label(#llvm_indirectbr{}) -> false;
is_label(#llvm_switch{}) -> false;
is_label(#llvm_invoke{}) -> false;
is_label(#llvm_operation{}) -> false;
is_label(#llvm_extractvalue{}) -> false;
is_label(#llvm_insertvalue{}) -> false;
is_label(#llvm_alloca{}) -> false;
is_label(#llvm_load{}) -> false;
is_label(#llvm_store{}) -> false;
is_label(#llvm_getelementptr{}) -> false;
is_label(#llvm_conversion{}) -> false;
is_label(#llvm_sitofp{}) -> false;
is_label(#llvm_ptrtoint{}) -> false;
is_label(#llvm_inttoptr{}) -> false;
is_label(#llvm_icmp{}) -> false;
is_label(#llvm_fcmp{}) -> false;
is_label(#llvm_phi{}) -> false;
is_label(#llvm_select{}) -> false;
is_label(#llvm_call{}) -> false;
is_label(#llvm_fun_def{}) -> false;
is_label(#llvm_fun_decl{}) -> false;
is_label(#llvm_landingpad{}) -> false;
is_label(#llvm_comment{}) -> false;
is_label(#llvm_const_decl{}) -> false;
is_label(#llvm_asm{}) -> false;
is_label(#llvm_adj_stack{}) -> false;
is_label(#llvm_meta{}) -> false.

%% const_decl
mk_const_decl(Dst, Decl_type, Type, Value) ->
  #llvm_const_decl{dst=Dst, decl_type=Decl_type, type=Type, value=Value}.
const_decl_dst(#llvm_const_decl{dst=Dst}) -> Dst.
const_decl_decl_type(#llvm_const_decl{decl_type=Decl_type}) -> Decl_type.
const_decl_type(#llvm_const_decl{type=Type}) -> Type.
const_decl_value(#llvm_const_decl{value=Value}) -> Value.

%% asm
mk_asm(Instruction) -> #llvm_asm{instruction=Instruction}.
asm_instruction(#llvm_asm{instruction=Instruction}) -> Instruction.

%% adj_stack
mk_adj_stack(Offset, Register, Type) ->
  #llvm_adj_stack{offset=Offset, 'register'=Register, type=Type}.
adj_stack_offset(#llvm_adj_stack{offset=Offset}) -> Offset.
adj_stack_register(#llvm_adj_stack{'register'=Register}) -> Register.
adj_stack_type(#llvm_adj_stack{type=Type}) -> Type.

%% meta-data
mk_meta(Id, Operands) ->
  #llvm_meta{id=Id, operands=Operands}.
meta_id(#llvm_meta{id=Id}) -> Id.
meta_operands(#llvm_meta{operands=Operands}) -> Operands.

%% types
mk_void() -> #llvm_void{}.

mk_label_type() -> #llvm_label_type{}.

mk_int(Width) -> #llvm_int{width=Width}.
int_width(#llvm_int{width=Width}) -> Width.

mk_double() -> #llvm_double{}.

mk_pointer(Type) -> #llvm_pointer{type=Type}.
pointer_type(#llvm_pointer{type=Type}) -> Type.

mk_array(Size, Type) -> #llvm_array{'size'=Size, type=Type}.
array_size(#llvm_array{'size'=Size}) -> Size.
array_type(#llvm_array{type=Type}) -> Type.

mk_vector(Size, Type) -> #llvm_vector{'size'=Size, type=Type}.
vector_size(#llvm_vector{'size'=Size}) -> Size.
vector_type(#llvm_vector{type=Type}) -> Type.

mk_struct(Type_list) -> #llvm_struct{type_list=Type_list}.
struct_type_list(#llvm_struct{type_list=Type_list}) -> Type_list.

mk_fun(Ret_type, Arg_type_list) ->
  #llvm_fun{ret_type=Ret_type, arg_type_list=Arg_type_list}.
function_ret_type(#llvm_fun{ret_type=Ret_type}) -> Ret_type.
function_arg_type_list(#llvm_fun{arg_type_list=Arg_type_list}) ->
  Arg_type_list.

%%----------------------------------------------------------------------------
%% Pretty-printer Functions
%%----------------------------------------------------------------------------

-type llvm_version() :: {Major :: integer(), Minor :: integer()}.

%% @doc Pretty-print a list of LLVM instructions to a Device, using syntax
%% compatible with LLVM v. Major.Minor
-spec pp_ins_list(file:io_device(), llvm_version(), [llvm_instr()]) -> ok.
pp_ins_list(_Dev, _Ver, []) -> ok;
pp_ins_list(Dev, Ver={_,_}, [I|Is]) ->
  pp_ins(Dev, Ver, I),
  pp_ins_list(Dev, Ver, Is).

pp_ins(Dev, Ver, I) ->
  case indent(I) of
    true  -> write(Dev, "  ");
    false -> ok
  end,
  case I of
    #llvm_ret{} ->
      write(Dev, "ret "),
      case ret_ret_list(I) of
        [] -> write(Dev, "void");
        List -> pp_args(Dev, List)
      end,
      write(Dev, "\n");
    #llvm_br{} ->
      write(Dev, ["br label ", br_dst(I), "\n"]);
    #llvm_switch{} ->
      write(Dev, "switch "),
      pp_type(Dev, switch_type(I)),
      write(Dev, [" ", switch_value(I), ", label ", switch_default_label(I),
                  " \n   [\n"]),
      pp_switch_value_label_list(Dev, switch_type(I),
                                 switch_value_label_list(I)),
      write(Dev, "    ]\n");
    #llvm_invoke{} ->
      write(Dev, [invoke_dst(I), " = invoke ", invoke_cconv(I), " "]),
      pp_options(Dev, invoke_ret_attrs(I)),
      pp_type(Dev, invoke_type(I)),
      write(Dev, [" ", invoke_fnptrval(I), "("]),
      pp_args(Dev, invoke_arglist(I)),
      write(Dev, ") "),
      pp_options(Dev, invoke_fn_attrs(I)),
      write(Dev, [" to label ", invoke_to_label(I)," unwind label ",
                  invoke_unwind_label(I), " \n"]);
    #llvm_br_cond{} ->
      write(Dev, ["br i1 ", br_cond_cond(I), ", label ", br_cond_true_label(I),
                  ", label ", br_cond_false_label(I)]),
      case br_cond_meta(I) of
        [] -> ok;
        Metadata ->
          write(Dev, [", !prof !", Metadata])
      end,
      write(Dev, "\n");
    #llvm_indirectbr{} ->
      write(Dev, "indirectbr "),
      pp_type(Dev, indirectbr_type(I)),
      write(Dev, [" ", indirectbr_address(I), ", [ "]),
      pp_args(Dev, indirectbr_label_list(I)),
      write(Dev, " ]\n");
    #llvm_operation{} ->
      write(Dev, [operation_dst(I), " = ", atom_to_list(operation_op(I)), " "]),
      case op_has_options(operation_op(I)) of
        true -> pp_options(Dev, operation_options(I));
        false -> ok
      end,
      pp_type(Dev, operation_type(I)),
      write(Dev, [" ", operation_src1(I), ", ", operation_src2(I), "\n"]);
    #llvm_extractvalue{} ->
      write(Dev, [extractvalue_dst(I), " = extractvalue "]),
      pp_type(Dev, extractvalue_type(I)),
      %% TODO Print idxs
      write(Dev, [" ", extractvalue_val(I), ", ", extractvalue_idx(I), "\n"]);
    #llvm_insertvalue{} ->
      write(Dev, [insertvalue_dst(I), " = insertvalue "]),
      pp_type(Dev, insertvalue_val_type(I)),
      write(Dev, [" ", insertvalue_val(I), ", "]),
      pp_type(Dev, insertvalue_elem_type(I)),
      %%TODO Print idxs
      write(Dev, [" ", insertvalue_elem(I), ", ", insertvalue_idx(I), "\n"]);
    #llvm_alloca{} ->
      write(Dev, [alloca_dst(I), " = alloca "]),
      pp_type(Dev, alloca_type(I)),
      case alloca_num(I) of
        [] -> ok;
        Num ->
          write(Dev, ", "),
          pp_type(Dev, alloca_type(I)),
          write(Dev, [" ", Num, " "])
      end,
      case alloca_align(I) of
        [] -> ok;
        Align -> write(Dev, [",align ", Align])
      end,
      write(Dev, "\n");
    #llvm_load{} ->
      write(Dev, [load_dst(I), " = "]),
      write(Dev, "load "),
      case load_volatile(I) of
        true -> write(Dev, "volatile ");
        false -> ok
      end,
      pp_dereference_type(Dev, Ver, load_p_type(I)),
      write(Dev, [" ", load_pointer(I), " "]),
      case load_alignment(I) of
        [] -> ok;
        Al -> write(Dev, [", align ", Al, " "])
      end,
      case load_nontemporal(I) of
        [] -> ok;
        In -> write(Dev, [", !nontemporal !", In])
      end,
      write(Dev, "\n");
    #llvm_store{} ->
      write(Dev, "store "),
      case store_volatile(I) of
        true -> write(Dev, "volatile ");
        false -> ok
      end,
      pp_type(Dev, store_type(I)),
      write(Dev, [" ", store_value(I), ", "]),
      pp_type(Dev, store_p_type(I)),
      write(Dev, [" ", store_pointer(I), " "]),
      case store_alignment(I) of
        [] -> ok;
        Al -> write(Dev, [", align ", Al, " "])
      end,
      case store_nontemporal(I) of
        [] -> ok;
        In -> write(Dev, [", !nontemporal !", In])
      end,
      write(Dev, "\n");
    #llvm_getelementptr{} ->
      write(Dev, [getelementptr_dst(I), " = getelementptr "]),
      case getelementptr_inbounds(I) of
        true -> write(Dev, "inbounds ");
        false -> ok
      end,
      pp_dereference_type(Dev, Ver, getelementptr_p_type(I)),
      write(Dev, [" ", getelementptr_value(I)]),
      pp_typed_idxs(Dev, getelementptr_typed_idxs(I)),
      write(Dev, "\n");
    #llvm_conversion{} ->
      write(Dev, [conversion_dst(I), " = ", atom_to_list(conversion_op(I)), " "]),
      pp_type(Dev, conversion_src_type(I)),
      write(Dev, [" ", conversion_src(I), " to "]),
      pp_type(Dev, conversion_dst_type(I)),
      write(Dev, "\n");
    #llvm_icmp{} ->
      write(Dev, [icmp_dst(I), " = icmp ", atom_to_list(icmp_cond(I)), " "]),
      pp_type(Dev, icmp_type(I)),
      write(Dev, [" ", icmp_src1(I), ", ", icmp_src2(I), "\n"]);
    #llvm_fcmp{} ->
      write(Dev, [fcmp_dst(I), " = fcmp ", atom_to_list(fcmp_cond(I)), " "]),
      pp_type(Dev, fcmp_type(I)),
      write(Dev, [" ", fcmp_src1(I), ", ", fcmp_src2(I), "\n"]);
    #llvm_phi{} ->
      write(Dev, [phi_dst(I), " = phi "]),
      pp_type(Dev, phi_type(I)),
      pp_phi_value_labels(Dev, phi_value_label_list(I)),
      write(Dev, "\n");
    #llvm_select{} ->
      write(Dev, [select_dst(I), " = select i1 ", select_cond(I), ", "]),
      pp_type(Dev, select_typ1(I)),
      write(Dev, [" ", select_val1(I), ", "]),
      pp_type(Dev, select_typ2(I)),
      write(Dev, [" ", select_val2(I), "\n"]);
    #llvm_call{} ->
      case call_dst(I) of
        [] -> ok;
        Dst -> write(Dev, [Dst, " = "])
      end,
      case call_is_tail(I) of
        true -> write(Dev, "tail ");
        false -> ok
      end,
      write(Dev, ["call ", call_cconv(I), " "]),
      pp_options(Dev, call_ret_attrs(I)),
      pp_type(Dev, call_type(I)),
      write(Dev, [" ", call_fnptrval(I), "("]),
      pp_args(Dev, call_arglist(I)),
      write(Dev, ") "),
      pp_options(Dev, call_fn_attrs(I)),
      write(Dev, "\n");
    #llvm_fun_def{} ->
      write(Dev, "define "),
      pp_options(Dev, fun_def_linkage(I)),
      pp_options(Dev, fun_def_visibility(I)),
      case fun_def_cconv(I) of
        [] -> ok;
        Cc -> write(Dev, [Cc, " "])
      end,
      pp_options(Dev, fun_def_ret_attrs(I)),
      write(Dev, " "),
      pp_type(Dev, fun_def_type(I)),
      write(Dev, [" @", fun_def_name(I), "("]),
      pp_args(Dev, fun_def_arglist(I)),
      write(Dev, ") "),
      pp_options(Dev, fun_def_fn_attrs(I)),
      case Ver >= {3,7} of false -> ok; true ->
	  write(Dev, "personality i32 (i32, i64, i8*,i8*)* "
		"@__gcc_personality_v0 ")
      end,
      case fun_def_align(I) of
        [] -> ok;
        N -> write(Dev, ["align ", N])
      end,
      write(Dev, "{\n"),
      pp_ins_list(Dev, Ver, fun_def_body(I)),
      write(Dev, "}\n");
    #llvm_fun_decl{} ->
      write(Dev, "declare "),
      pp_options(Dev, fun_decl_linkage(I)),
      pp_options(Dev, fun_decl_visibility(I)),
      case fun_decl_cconv(I) of
        [] -> ok;
        Cc -> write(Dev, [Cc, " "])
      end,
      pp_options(Dev, fun_decl_ret_attrs(I)),
      pp_type(Dev, fun_decl_type(I)),
      write(Dev, [" ", fun_decl_name(I), "("]),
      pp_type_list(Dev, fun_decl_arglist(I)),
      write(Dev, ") "),
      case fun_decl_align(I) of
        [] -> ok;
        N -> write(Dev, ["align ", N])
      end,
      write(Dev, "\n");
    #llvm_comment{} ->
      write(Dev, ["; ", atom_to_list(comment_text(I)), "\n"]);
    #llvm_label{} ->
      write(Dev, [label_label(I), ":\n"]);
    #llvm_const_decl{} ->
      write(Dev, [const_decl_dst(I), " = ", const_decl_decl_type(I), " "]),
      pp_type(Dev, const_decl_type(I)),
      write(Dev, [" ", const_decl_value(I), "\n"]);
    #llvm_landingpad{} ->
      write(Dev, "landingpad { i8*, i32 } "),
      case Ver < {3,7} of false -> ok; true ->
	  write(Dev, "personality i32 (i32, i64, i8*,i8*)* "
		"@__gcc_personality_v0 ")
      end,
      write(Dev, "cleanup\n");
    #llvm_asm{} ->
      write(Dev, [asm_instruction(I), "\n"]);
    #llvm_adj_stack{} ->
      write(Dev, ["call void asm sideeffect \"sub $0, ",
          adj_stack_register(I), "\", \"r\"("]),
      pp_type(Dev, adj_stack_type(I)),
      write(Dev, [" ", adj_stack_offset(I),")\n"]);
    #llvm_meta{} ->
      write(Dev, ["!", meta_id(I), " = "]),
      Named = case string:to_integer(meta_id(I)) of
		{_, ""} -> false;
		_ -> true
	      end,
      case Ver < {3,6} andalso not Named of
	true -> write(Dev, "metadata !{metadata ");
	false -> write(Dev, "!{ ")
      end,
      write(Dev, string:join([if is_list(Op) -> ["!\"", Op, "\""];
				 is_integer(Op) -> ["i32 ", integer_to_list(Op)];
				 is_record(Op, llvm_meta) ->
				  ["!", meta_id(Op)]
			      end || Op <- meta_operands(I)], ", ")),
      write(Dev, " }\n");
    Other ->
      exit({?MODULE, pp_ins, {"Unknown LLVM instruction", Other}})
  end.

%% @doc Print the type of a dereference in an LLVM instruction using syntax
%% parsable by the specified LLVM version.
pp_dereference_type(Dev, Ver, Type) ->
  case Ver >= {3,7} of
    false -> ok;
    true ->
      pp_type(Dev, pointer_type(Type)),
      write(Dev, ", ")
  end,
  pp_type(Dev, Type).

%% @doc Pretty-print a list of types
pp_type_list(_Dev, []) -> ok;
pp_type_list(Dev, [T]) ->
  pp_type(Dev, T);
pp_type_list(Dev, [T|Ts]) ->
  pp_type(Dev, T),
  write(Dev, ", "),
  pp_type_list(Dev, Ts).

pp_type(Dev, Type) ->
  case Type of
    #llvm_void{} ->
      write(Dev, "void");
    #llvm_label_type{} ->
      write(Dev, "label");
    %% Integer
    #llvm_int{} ->
      write(Dev, ["i", integer_to_list(int_width(Type))]);
    %% Float
    #llvm_float{} ->
      write(Dev, "float");
    #llvm_double{} ->
      write(Dev, "double");
    #llvm_fp80{} ->
      write(Dev, "x86_fp80");
    #llvm_fp128{} ->
      write(Dev, "fp128");
    #llvm_ppc_fp128{} ->
      write(Dev, "ppc_fp128");
    %% Pointer
    #llvm_pointer{} ->
      pp_type(Dev, pointer_type(Type)),
      write(Dev, "*");
    %% Function
    #llvm_fun{} ->
      pp_type(Dev, function_ret_type(Type)),
      write(Dev, " ("),
      pp_type_list(Dev, function_arg_type_list(Type)),
      write(Dev, ")");
    %% Aggregate
    #llvm_array{} ->
      write(Dev, ["[", integer_to_list(array_size(Type)), " x "]),
      pp_type(Dev, array_type(Type)),
      write(Dev, "]");
    #llvm_struct{} ->
      write(Dev, "{"),
      pp_type_list(Dev, struct_type_list(Type)),
      write(Dev, "}");
    #llvm_vector{} ->
      write(Dev, ["{", integer_to_list(vector_size(Type)), " x "]),
      pp_type(Dev, vector_type(Type)),
      write(Dev, "}")
  end.

%% @doc Pretty-print a list of typed arguments
pp_args(_Dev, []) -> ok;
pp_args(Dev, [{Type, Arg} | []]) ->
  pp_type(Dev, Type),
  write(Dev, [" ", Arg]);
pp_args(Dev, [{Type, Arg} | Args]) ->
  pp_type(Dev, Type),
  write(Dev, [" ", Arg, ", "]),
  pp_args(Dev, Args).

%% @doc Pretty-print a list of options
pp_options(_Dev, []) -> ok;
pp_options(Dev, [O|Os]) ->
  write(Dev, [atom_to_list(O), " "]),
  pp_options(Dev, Os).

%% @doc Pretty-print a list of phi value-labels
pp_phi_value_labels(_Dev, []) -> ok;
pp_phi_value_labels(Dev, [{Value, Label}|[]]) ->
  write(Dev, ["[ ", Value, ", ", Label, " ]"]);
pp_phi_value_labels(Dev,[{Value, Label}|VL]) ->
  write(Dev, ["[ ", Value, ", ", Label, " ], "]),
  pp_phi_value_labels(Dev, VL).

%% @doc Pretty-print a list of typed indexes
pp_typed_idxs(_Dev, []) -> ok;
pp_typed_idxs(Dev, [{Type, Id} | Tids]) ->
  write(Dev, ", "),
  pp_type(Dev, Type),
  write(Dev, [" ", Id]),
  pp_typed_idxs(Dev, Tids).

%% @doc Pretty-print a switch label list
pp_switch_value_label_list(_Dev, _Type,  []) -> ok;
pp_switch_value_label_list(Dev, Type, [{Value, Label} | VLs]) ->
  write(Dev, "      "),
  pp_type(Dev, Type),
  write(Dev, [" ", Value, ", label ", Label, "\n"]),
  pp_switch_value_label_list(Dev, Type, VLs).

%%----------------------------------------------------------------------------
%% Auxiliary Functions
%%----------------------------------------------------------------------------

%% @doc Returns if an instruction needs to be intended
indent(I) ->
  case I of
    #llvm_label{} -> false;
    #llvm_fun_def{} -> false;
    #llvm_fun_decl{} -> false;
    #llvm_const_decl{} -> false;
    #llvm_meta{} -> false;
    _ -> true
  end.

op_has_options(Op) ->
  case Op of
    'and' -> false;
    'or' -> false;
    'xor' -> false;
    _ -> true
  end.

%% @doc Abstracts actual writing to file operations
write(Dev, Msg) ->
  ok = file:write(Dev, Msg).
