(** Bombs-Must-Detonate: IR Compiler @author: Brian Go*) (** Final instruction *) type instruction = (** 0-arity *) Push | Pop | Read | Print | PrintLn | Swap | Stop | Return | Cons | Car | Cdr | IsNull | Nil | GetElem | SetElem | And | Or | Add | Sub | Mul | Div | DivI | RemI | Concat | Neg | Frac | Int | Lt | Gt | Lte | Gte | Eq | Neq | ConstUninit | Apply (** 1-arity *) | ConstInt of int | ConstFloat of float | ConstString of string | PushSf of string | Assign of int | Acc of int | Rev of int | MakeBlock of int | MakeBlockFilled of int | AllocFields of int | GetField of int | SetField of int | Call of string | Jmp of string | Jz of string | Jnz of string | RPC of string | Label of string | Comment of string | Annotation of string | GetElemStatic of int | SetElemStatic of int (** 2-arity *) | MakeBlockStatic of int * int (** IR Instruction *) type ir_instruction = Instruction of instruction (** Ir-Specific *) | GetVar of string | SetVar of string | NoteVar of string * int | NoteFunction of string | If of ir_buffer * ir_buffer * ir_buffer (** test, then, else *) | For of ir_buffer * ir_buffer * ir_buffer * ir_buffer | While of ir_buffer * ir_buffer | DoWhile of ir_buffer * ir_buffer | Break | Continue | BeginScope | EndScope | EndScopeSf | EndScopeSfRPC | DeclareGlobal of string and ir_buffer = ir_instruction list (** Shorthand for String.concat "" *) let concat s_list = String.concat "" s_list exception Ircompile_error of string (** Keeps track of the buffer of code used to initialize structs/arrays/lists. We need to initialize values of these types as null but of the correct structure. i.e. someStructType someStruct; stomeStruct.someField = someValue; should be valid. *) let init_buffer = ref [] (** Raises an IR compile error by concatenating s_list *) let raise_compile_error s_list = raise (Ircompile_error (concat s_list)) (** Concatenates n repetitions of the given list *) let rec repeat_list lst n = if n = 0 then [] else if n = 1 then lst else if n > 1 then lst @ (repeat_list lst (n-1)) else raise (Invalid_argument "repeat_list must have a nonnegative argument") (** Used by make_label *) let label_id = ref (-1) (** Generate labels with unique prefixes *) let make_label str = let _ = label_id := !label_id + 1 in concat ["_";string_of_int !label_id;"_";str] (** Takes an argument list and notes the positions *) let rec note_arg_list lst index = match lst with (_,varname)::rest -> (NoteVar (varname,index))::(note_arg_list rest (index+1)) | [] -> [] (** Compiles the initialization code of data type dt *) let rec compile_datatype_init dt = match dt with (Ast.SynIntType | Ast.SynFloatType | Ast.SynStringType | Ast.SynBoolType | Ast.SynVoidType | Ast.SynRefType _) -> [Instruction ConstUninit] | Ast.SynEnumOrStructType (name,field_types) -> (** field_types is empty if its an enum/template *) if List.length !field_types > 0 then let n_fields = List.length !field_types in let fields_result = compile_datatypes_pushing !field_types in fields_result @ [Instruction (Rev n_fields)] @ [Instruction (MakeBlockStatic (1,n_fields))] else [Instruction ConstUninit] | Ast.SynArrayType (t_arr,vprod) -> let dt_code = compile_datatype_init t_arr in let vprod_code = compile_value_producer vprod in dt_code @ [Instruction Push] @ vprod_code @ [Instruction (MakeBlockFilled 0)] | Ast.SynListType _ -> [Instruction Nil] | Ast.SynArrowType (_,_) -> [Instruction (ConstString "")] (** Compiles each data type in dt_list, inserting push instructions between each compilation result *) and compile_datatypes_pushing dt_list = match dt_list with dt::rest -> (compile_datatype_init dt)@[Instruction Push]@(compile_datatypes_pushing rest) | [] -> [] (** Compiles an expression *) and compile_expression expr = match expr with Ast.SynVarDeclare vdecl -> compile_variable_declaration vdecl | Ast.SynEnumDeclare edecl -> [] | Ast.SynVarAssign vasgn -> compile_variable_assignment vasgn | Ast.SynCond cond -> compile_conditional cond | Ast.SynLoop loop -> compile_loop loop | Ast.SynFunctionCall funcall -> compile_function_call funcall | Ast.SynReturnStatement ret -> compile_return ret | Ast.SynBreak -> [Break] | Ast.SynContinue -> [Continue] (** Compiles a list of expressions *) and compile_expression_list expr_list = match expr_list with cur::rest -> let curbuf = compile_expression cur in let restbuf = compile_expression_list rest in curbuf@restbuf | [] -> [] (** Compiles a variable declarations *) and compile_variable_declaration vdecl = match vdecl with Ast.SynVarDeclareNoInit (dt, varname) -> let dt_init = compile_datatype_init dt in dt_init @ [SetVar varname] | Ast.SynVarDeclareWithInit(_, varname, vprod) -> let valbuf = compile_value_producer vprod in valbuf@[SetVar varname] (** Compiles a conditional. Would be nice to add short-circuiting in the future. *) and compile_conditional cond = match cond with Ast.SynIf (vp,expr_list) -> let vp_result = compile_value_producer vp in let expr_result = compile_expression_list expr_list in [If (vp_result, expr_result, [])] | Ast.SynIfCase (vp,expr_list,continued_cond) -> let vp_result = compile_value_producer vp in let expr_result = compile_expression_list expr_list in let continued_result = compile_continued_conditional continued_cond in [If (vp_result, expr_result, continued_result)] (** Compiles an else case *) and compile_continued_conditional cond = match cond with Ast.SynFinalElse expr_list -> compile_expression_list expr_list | Ast.SynElse cond -> compile_conditional cond (** Compiles a loop *) and compile_loop loop = match loop with Ast.SynWhile (vprod,lexpr_list) -> let vp_result = compile_value_producer vprod in let lexpr_result = compile_expression_list lexpr_list in [While (vp_result,lexpr_result)] | Ast.SynFor (init,test,step,body) -> let init_result = compile_expression init in let test_result = compile_value_producer test in let step_result = compile_expression step in let body_result = compile_expression_list body in [For (init_result, test_result, step_result, body_result)] | Ast.SynDoWhile (lexpr_list, vprod) -> let vp_result = compile_value_producer vprod in let lexpr_result = compile_expression_list lexpr_list in [While (lexpr_result,vp_result)] (** Compiles a value *) and compile_value value = match value with Ast.SynIntValue i -> [Instruction (ConstInt i)] | Ast.SynFloatValue f -> [Instruction (ConstFloat f)] | Ast.SynBoolValue b -> [Instruction (ConstInt (if b then 1 else 0))] | Ast.SynStringValue s -> [Instruction (ConstString s)] (** Compiles a value produder *) and compile_value_producer vprod = match vprod with Ast.SynValue value -> compile_value value | Ast.SynFunctionCallValue funcall -> compile_function_call funcall | Ast.SynVarIdentifier vident -> compile_get_variable_identifier vident | Ast.SynBinop (vp1,op,vp2) -> compile_binary_operation vp1 op vp2 | Ast.SynPrefixUnop (op,vp) -> compile_prefix_unary_operation op vp | Ast.SynParenthesized vprod -> compile_value_producer vprod | Ast.SynArrayValueProducer vprod_list -> let n = List.length vprod_list in let values_result = compile_value_producer_list_pushing vprod_list in values_result @ [Instruction (Rev n); Instruction (MakeBlockStatic (0,n))] | Ast.SynListValueProducer lvprod -> compile_list_value_producer lvprod (** Compiles each value producer in order, pushing the results onto the stack *) and compile_value_producer_list_pushing vp_list = match vp_list with cur::rest -> let cur_result = compile_value_producer cur in let rest_result= compile_value_producer_list_pushing rest in cur_result @ [Instruction Push] @ rest_result | [] -> [] (** Compiles a list value producer *) and compile_list_value_producer lvprod = match lvprod with Ast.SynListNil _ -> [Instruction Nil] | Ast.SynListList vprod_list -> let result = compile_value_producer_list_pushing vprod_list in result @ [Instruction Nil] @ (repeat_list [Instruction Cons] (List.length vprod_list)) | Ast.SynListCons (vp1,vp2) -> let result_car = compile_value_producer vp1 in let result_cdr = compile_value_producer vp2 in result_car @ [Instruction Push] @ result_cdr @ [Instruction Cons] (** Compiles a variable assignment *) and compile_variable_assignment vasgn = match vasgn with Ast.SynVarAssignment (vident,vprod) -> let vp_result = compile_value_producer vprod in let setvar_result = compile_set_variable_identifier vident in vp_result @ setvar_result | Ast.SynVarModify (vident, op, vprod) -> let vp1, vp2 = Ast.SynVarIdentifier vident, vprod in let op_result = compile_value_producer (Ast.SynBinop (vp1, op, vp2)) in let setvar_result = compile_set_variable_identifier vident in op_result @ setvar_result (** Produces code that sets the variable identifier to the value in the accumulator. Leaves the value in the accumulator *) and compile_set_variable_identifier vident = match vident with Ast.SynVarName s -> [SetVar s] | Ast.SynStructOrEnumValue (vid1,s2,ftype) -> (match !ftype with Ast.VIdStructField index -> let getstruct = compile_get_variable_identifier vid1 in [Instruction Push] @ getstruct @ [Instruction (SetElemStatic index)] (** structs are reference types; we're done at this point *) | Ast.VIdEnumValue _ -> raise_compile_error ["Unexpected compile error: cannot set an enum value."] | Ast.VIdUnspecified -> raise_compile_error ["Unexpected compile error: field index ";s2;" was not determined by type checker."]) | Ast.SynArrayCell (vid,vprod) -> let vid_result = compile_get_variable_identifier vid in let vprod_result = compile_value_producer vprod in [Instruction Push] @ vprod_result @ [Instruction Push] @ vid_result @ [Instruction SetElem] (** Get a variable identifier into the accumulator *) and compile_get_variable_identifier vident = match vident with Ast.SynVarName s -> [GetVar s] | Ast.SynStructOrEnumValue (vid1,s2,ftype) -> (match !ftype with Ast.VIdStructField index -> let getstruct = compile_get_variable_identifier vid1 in getstruct @ [Instruction (GetElemStatic index)] | Ast.VIdEnumValue index -> [Instruction (ConstInt index)] | Ast.VIdUnspecified -> raise_compile_error ["Unexpected compile error: field index ";s2;" was not determined by type checker."]) | Ast.SynArrayCell (vid,vprod) -> let vid_result = compile_get_variable_identifier vid in let vprod_result = compile_value_producer vprod in vprod_result @ [Instruction Push] @ vid_result @ [Instruction GetElem] (** Compiles a binary operation expression *) and compile_binary_operation vp1 op vp2 = let vp1_result = compile_value_producer vp1 in let vp2_result = compile_value_producer vp2 in let op_command = match op with Ast.SynBinopAnd -> [Instruction And] | Ast.SynBinopOr -> [Instruction Or] | Ast.SynBinopAdd -> [Instruction Add] | Ast.SynBinopSub -> [Instruction Sub] | Ast.SynBinopMul -> [Instruction Mul] | Ast.SynBinopDiv -> [Instruction Div] | Ast.SynBinopIDiv ->[Instruction DivI] | Ast.SynBinopMod -> [Instruction RemI] | Ast.SynBinopConcat -> [Instruction Concat] | Ast.SynBinopCons -> [Instruction Cons] | Ast.SynCompLt -> [Instruction Lt] | Ast.SynCompGt -> [Instruction Gt] | Ast.SynCompLte ->[Instruction Lte] | Ast.SynCompGte ->[Instruction Gte] | Ast.SynCompEq -> [Instruction Eq] | Ast.SynCompNeq ->[Instruction Neq] in vp2_result @ [Instruction Push] @ vp1_result @ op_command (** Compiles unary operations *) and compile_prefix_unary_operation op vp = let vp_result = compile_value_producer vp in let op_command = match op with Ast.SynUnopNot -> [Instruction Push; Instruction (ConstInt 1); Instruction Add; Instruction Push; Instruction (ConstInt 2); Instruction Swap; Instruction RemI] | Ast.SynUnopCar -> [Instruction Car] | Ast.SynUnopCdr -> [Instruction Cdr] | Ast.SynUnopTrunc -> [Instruction Int] | Ast.SynUnopDeref -> [Instruction (GetElemStatic 0)] | Ast.SynUnopNeg -> [Instruction Neg] | Ast.SynUnopNull -> [Instruction IsNull] in vp_result @ op_command (** Compiles a function call. Doesn't use tail-recursion -- this would be nice to add in the future. *) and compile_function_call funcall = match funcall with Ast.SynLocalCall (fname,vprod_list,isvar) -> let n_args = List.length vprod_list in let args_result = compile_value_producer_list_pushing vprod_list in let ret_label = make_label (concat ["returnfrom_local_";fname]) in let call_instruction = if !isvar then [GetVar fname; Instruction Apply] else [Instruction (Call fname)] in [Instruction (Comment (concat ["Local call to ";fname])); Instruction (PushSf ret_label); BeginScope] @ args_result @ [Instruction (Rev n_args)] @ call_instruction @ [EndScopeSf; Instruction (Label ret_label)] | Ast.SynRemoteCall (fname,vprod_list,ret_var) -> let n_args = List.length vprod_list in let args_result = compile_value_producer_list_pushing vprod_list in let ret_label = make_label (concat ["returnfrom_remote_withresult_";fname]) in let done_label = make_label (concat ["done_interpreting_remote_results_";fname]) in let vident_set_result = compile_set_variable_identifier ret_var in [Instruction (Comment (concat ["Remote call with result to ";fname])); Instruction (PushSf ret_label); BeginScope] @ args_result @ [Instruction (Rev n_args); Instruction (RPC fname); EndScopeSfRPC; Instruction (Label ret_label); (** Acc || Stack = success || result *) Instruction (Jz done_label); Instruction Swap] (** result || success *) @ vident_set_result (** ? || success *) @ [Instruction Swap; (** success || ? *) Instruction (Label done_label); (** success || ? *) Instruction Swap; (** ? || success *) Instruction (Acc 0); (** success || success *) Instruction Pop] (** success || *) | Ast.SynRemoteCallNoResult (fname, vprod_list) -> let n_args = List.length vprod_list in let args_result = compile_value_producer_list_pushing vprod_list in let ret_label = make_label (concat ["returnfrom_local_";fname]) in [Instruction (Comment (concat ["Remote call with no result to ";fname])); Instruction (PushSf ret_label); BeginScope] @ args_result @ [Instruction (Rev n_args); Instruction (RPC fname); EndScopeSfRPC; Instruction (Label ret_label); (** success || result *) Instruction Swap; (** result || success *) Instruction (Acc 0); (** success || success *) Instruction Pop] (** success || *) (** Compiles a return statement *) and compile_return ret = match ret with Ast.SynVoidReturn -> [Instruction ConstUninit; Instruction Return] | Ast.SynValueReturn vprod -> let vp_result = compile_value_producer vprod in vp_result@[Instruction Return] (** Compiles a function definition. Maybe this should have gone to IR first, but it works nicely as is. *) let rec compile_function_definition name arg_list expr_list = let label = make_label name in let args = note_arg_list arg_list 0 in let body_code = compile_expression_list expr_list in [NoteFunction name; Instruction (Annotation (concat ["function ";name;" ";string_of_int (List.length arg_list)])); (** annotate *) Instruction (Label label); BeginScope] @ args @ body_code @ [Instruction (Comment (concat ["end function ";name])); Instruction ConstUninit; Instruction Return; EndScope] (** Compile global expression *) let rec compile_global_expression expr = match expr with Ast.SynFunctionDeclare (dt, name, arglist) -> [NoteFunction name] | Ast.SynRemotableFunctionDeclare (dt, name, arglist) -> [] | Ast.SynFunctionDefine ((dt, name, arglist), expr_list) -> compile_function_definition name arglist expr_list | Ast.SynTemplatedDeclare (_,expr) -> compile_global_expression expr | Ast.SynTemplatedDefine (_,expr) -> compile_global_expression expr | Ast.SynStructDeclare sdecl -> [] | Ast.SynGlobalEnumDeclare edecl -> [] | Ast.SynGlobalVarDeclare vdecl -> (match vdecl with Ast.SynVarDeclareNoInit (dt,v) -> let dt_result = compile_datatype_init dt in let _ = init_buffer := !init_buffer @ dt_result @ [SetVar v] in [DeclareGlobal v] | _ -> raise_compile_error ["Unexpected compile error: cannot initialize a variable in the global scope."]) | Ast.SynStateMachine _ -> raise_compile_error ["Unexpected compile error: source-to-source compilation of state machine failed."] | Ast.SynInclude data -> match !data with Ast.IncludeAst ast -> compile_global_expression_list ast | Ast.IncludeFileName s -> raise_compile_error ["Unexpected compile error: include ";s;" was not typechecked properly."] (** Compile a global expression list *) and compile_global_expression_list src_prog = match src_prog with cur::rest -> let curbuf = compile_global_expression cur in let restbuf = compile_global_expression_list rest in curbuf@restbuf | [] -> [] (** Compile a source program to IR representation, including the init buffer *) and compile_ir src_prog = try let _ = init_buffer := [] in let init_label = make_label "INIT" in let program_result = compile_global_expression_list src_prog in [Instruction (Annotation (concat ["init ";init_label]))] @ program_result @ [Instruction (Annotation (concat ["function ";init_label;" 0"])); Instruction (Label (init_label))] @ !init_buffer @ [Instruction Return] with Ircompile_error s -> let _ = output_string stderr s in let _ = output_string stderr "\n" in let _ = flush stderr in raise (Ircompile_error s) (** Produces a string representation of the given instruction *) let string_of_instruction inst = match inst with Push -> "Push" | Pop -> "Pop" | Read -> "Read" | Print -> "Print" | PrintLn -> "PrintLn" | Swap -> "Swap" | Stop -> "Stop" | Return -> "Return" | Cons -> "Cons" | Car -> "Car" | Cdr -> "Cdr" | IsNull -> "IsNull" | Nil -> "Nil" | GetElem -> "GetElem" | SetElem -> "SetElem" | And -> "And" | Or -> "Or" | Add -> "Add" | Sub -> "Sub" | Mul -> "Mul" | Div -> "Div" | DivI -> "DivI" | RemI -> "RemI" | Concat -> "Concat" | Neg -> "Neg" | Frac -> "Frac" | Int -> "Int" | Lt -> "Lt" | Gt -> "Gt" | Lte -> "Lte" | Gte -> "Gte" | Eq -> "Eq" | Neq -> "Neq" | ConstUninit -> "Const" | ConstInt i -> concat ["Const ";string_of_int i] | ConstFloat f -> concat ["Const ";string_of_float f] | ConstString s -> concat ["Const \"";s;"\""] | PushSf s -> concat ["PushSf ";s] | Assign i -> concat ["Assign ";string_of_int i] | Acc i -> concat ["Acc ";string_of_int i] | Rev i -> concat ["Rev ";string_of_int i] | MakeBlock i -> concat ["MakeBlock ";string_of_int i] | AllocFields i -> concat ["-allocfields, ";string_of_int i] | GetField i -> concat ["GetField ";string_of_int i] | SetField i -> concat ["SetField ";string_of_int i] | Call s -> concat ["Call ";s] | Jmp s -> concat ["Jmp ";s] | Jz s -> concat ["Jz ";s] | Jnz s -> concat ["Jnz ";s] | RPC s -> concat ["RPC ";s] | Label s -> concat [s;": "] | Comment s -> concat ["//";s] | Annotation s -> concat ["-";s] | GetElemStatic i -> concat ["GetElem ";string_of_int i] | SetElemStatic i -> concat ["SetElem ";string_of_int i] | MakeBlockStatic (i,j) -> concat ["MakeBlockStatic ";string_of_int i;" ";string_of_int j] | MakeBlockFilled n -> concat ["MakeFilledBlock ";string_of_int n] | Apply -> "Apply" (** Produces a string representation of the given IR instruction *) let rec string_of_ir_instruction inst = match inst with Instruction i -> string_of_instruction i | GetVar s -> concat ["GetVar ";s] | SetVar s -> concat ["SetVar ";s] | NoteVar (s,i) -> concat ["NoteVar ";s;" ";string_of_int i] | If (test,body,elsebody) -> concat ["IF\n"; string_of_ir_buffer test; "\nTHEN\n"; string_of_ir_buffer body; "\nELSE\n"; string_of_ir_buffer elsebody; "\nENDIF\n"] | For (init,test,step,body) -> concat ["FOR\nINIT\n"; string_of_ir_buffer init; "\nTEST IF TRUE\n"; string_of_ir_buffer test; "\nSTEP\n"; string_of_ir_buffer step; "\nDO\n"; string_of_ir_buffer body; "\nENDFOR\n"] | While (test,body) -> concat ["WHILE\nTEST IF TRUE\n"; string_of_ir_buffer test; "\nDO\n"; string_of_ir_buffer body; "\nENDWHILE\n"] | DoWhile (body,test) -> concat ["WHILE\nTEST IF TRUE\n"; string_of_ir_buffer test; "\nDO\n"; string_of_ir_buffer body; "\nENDWHILE\n"] | Break -> "Break" | Continue -> "Continue" | DeclareGlobal s -> concat ["DeclareGlobal ";s] | EndScope -> "EndScope" | EndScopeSf -> "EndScopeSf" | BeginScope -> "BeginScope" | EndScopeSfRPC -> "EndScopeSfRPC" | NoteFunction s -> concat ["NoteFunction ";s] (** Produces a string representation of the given IR instruction buffer *) and string_of_ir_buffer buf = match buf with cur::rest -> concat [(string_of_ir_instruction cur);"\n";(string_of_ir_buffer rest)] | [] -> "" (** Prints the IR buffer to the given output channel *) and print_ir_buffer chan buf = output_string chan (string_of_ir_buffer buf)