浏览代码

Add method inlining to inline constructors filter (#9599)

* Remove unused decls array

* Mark inline objects using metadata instead of identifying them by order.

* Remove unused e.etype from match

* Replace e with mark_ctors result.

* Fix mark_ctors

* Remove unused id_start and id_end

* debug hacks

* Make handle field case handle inline methods too.

* Implement inline constructor method inlining (Has known bugs marked with TODO)

* Remove debug prints.

* Fix typing of ethis on method inline.

* For now don't forward captured to the inlined method analysis

* Add missing result of this expression to inlined method.

* Improve names of some variables.

* Analyze aliases in call args of inlined methods and cancel the resulting inline objects if the parent inline object is cancelled.

* Remove commented code.

* Support returning inline objects from inlined methods

* remove debug stuff.

* Factor out call arg analysis

* Some documentation.

* Add return type to mark_ctors

* Set io_has_untyped if inlined method had HasUntyped

* Add @:inlineObject to meta.json and use that instead of Meta.Custom

* Add more comments

* Add inline constructor tests.

* Use record for IOKCtor data

* Check that the expression has inline objects before doing any work.

* Make sure the field we are inlining is a compatible function

This can only happen when dynamic casts are involved.
This is probably over-conservative, instead of type_eq_strict it would be better to check if it unifies.

* Add test for incompatible method inline cancellations.

* Check that the class method type unifies with the field expression type instead.

Also check that the field expression is a function to avoid the Dynamic case.
Mario Carbajal 5 年之前
父节点
当前提交
0d4f2f2c75

+ 6 - 0
src-json/meta.json

@@ -573,6 +573,12 @@
 		"doc": "Internally used to mark expressions that were passed as arguments of an inlined constructor.",
 		"doc": "Internally used to mark expressions that were passed as arguments of an inlined constructor.",
 		"internal": true
 		"internal": true
 	},
 	},
+	{
+		"name": "InlineObject",
+		"metadata": ":inlineObject",
+		"doc": "Internally used by inline constructors filter to mark potentially inlineable objects.",
+		"internal": true
+	},
 	{
 	{
 		"name": "Internal",
 		"name": "Internal",
 		"metadata": ":internal",
 		"metadata": ":internal",

+ 374 - 200
src/optimization/inlineConstructors.ml

@@ -29,8 +29,7 @@ open Globals
 (*
 (*
 	First pass:
 	First pass:
 	Finds all inline objects and variables that alias them.
 	Finds all inline objects and variables that alias them.
-	Inline objects reference instances of TNew TObjectDecl and TArrayDecl, identified a number
-	assigned by order of appearance in the expression.
+	Inline objects reference instances of TNew TObjectDecl and TArrayDecl.
 	When an inline object is assigned to a variable, this variable is considered an alias of it.
 	When an inline object is assigned to a variable, this variable is considered an alias of it.
 	If an aliasing variable is assigned more than once then inlining will be cancelled for the inline
 	If an aliasing variable is assigned more than once then inlining will be cancelled for the inline
 	object the variable would have aliased.
 	object the variable would have aliased.
@@ -42,48 +41,73 @@ open Globals
 	inlining.
 	inlining.
 
 
 	Second pass:
 	Second pass:
-	Replace variables that alias inline objects with their respective field inline variables.
-	Identify inline objects by order of appearance and replace them with their inlined constructor expressions.
-	Replace field access of aliasing variables with the respective field inline variable.
+	Replaces inline objects with their inlined constructor expressions.
+	Replaces field access of aliasing variables with the respective field inline variable or inlined methods.
 	Because some replacements turn a single expression into many, this pass will map texpr into texpr list,
 	Because some replacements turn a single expression into many, this pass will map texpr into texpr list,
 	which is converted into TBlocks by the caller as needed.
 	which is converted into TBlocks by the caller as needed.
 *)
 *)
 
 
-type inline_object_kind =
-	| IOKCtor of tclass_field * bool * tvar list
+type inline_object_ctor = {
+	ioc_class : Type.tclass;
+	ioc_tparams : Type.tparams;
+	ioc_field : Type.tclass_field;
+	ioc_forced : bool; (* Cancelling a forced constructor should produce an error *)
+}
+
+and inline_object_kind =
+	| IOKCtor of inline_object_ctor
 	| IOKStructure
 	| IOKStructure
 	| IOKArray of int
 	| IOKArray of int
 
 
+(*
+	inline_object
+	Represents an instance of TNew TObjectDecl or TArrayDecl that has potential to be inlined.
+	Wether the inlining is cancelled or not is decided during the analysis phase.
+*)
 and inline_object = {
 and inline_object = {
 	io_kind : inline_object_kind;
 	io_kind : inline_object_kind;
-	io_expr : texpr;
-	io_pos : pos;
-	io_has_untyped : bool;
-	mutable io_cancelled : bool;
-	mutable io_declared : bool;
-	mutable io_aliases : inline_var list;
-	mutable io_fields : (string,inline_var) PMap.t;
-	mutable io_id_start : int;
-	mutable io_id_end : int;
+	io_expr : texpr; (* This is the inlined constructor expression *)
+	io_pos : pos; (* The original position of the constructor expression *)
+	mutable io_has_untyped : bool; (* Wether inlining this object would bring untyped expressions into the parent expression *)
+	mutable io_cancelled : bool; (* Wether this inline object has been cancelled *)
+	mutable io_declared : bool; (* Wether the variable declarations for this inline object's fields have already been output. (Used in final_map) *)
+	mutable io_aliases : inline_var list; (* List of variables that are aliasing/referencing this inline object *)
+	mutable io_fields : (string,inline_var) PMap.t; (* The fields that this inline object supports, fields are inline variables which might alias other inline_objects *)
+	mutable io_inline_methods : texpr list; (* List of inlined method calls. Populated during analysis and consumed by the final_map phase *)
+	mutable io_dependent_vars : tvar list; (* List of variables that should be cancelled if this inline object is cancelled *)
 }
 }
 
 
 and inline_var_kind =
 and inline_var_kind =
-	| IVKField of inline_object * string * texpr option
+	| IVKField of
+		inline_object *
+		string * (* The field name *)
+		texpr option (* If this is Some _ then this field is a constant. (Used for Array .length property) *)
 	| IVKLocal
 	| IVKLocal
 
 
 and inline_var_state =
 and inline_var_state =
-	| IVSUnassigned
-	| IVSAliasing of inline_object
-	| IVSCancelled
+	| IVSUnassigned (* The variable isn't yet assigned *)
+	| IVSAliasing of inline_object (* The variable is aliasing an inline object *)
+	| IVSCancelled (* The variable was cancelled and should no longer be considered for aliasing inline objects *)
 
 
+(*
+	inline_var
+	Represents a local variable that is tracked by the inline constructor analysis.
+	It's main purpose is to track variables that are considered aliases of inline objects.
+	It also tracks all unassigned variables in the program.
+*)
 and inline_var = {
 and inline_var = {
 	iv_var : tvar;
 	iv_var : tvar;
 	mutable iv_state : inline_var_state;
 	mutable iv_state : inline_var_state;
 	mutable iv_kind : inline_var_kind;
 	mutable iv_kind : inline_var_kind;
-	mutable iv_closed : bool
+	mutable iv_closed : bool (* Inline variables are marked as closed when the scope they were first assigned on ends, any appearance of this variable after it has been closed causes cancellation *)
 }
 }
 
 
-let inline_constructors ctx e =
+and inline_object_field = 
+	| IOFInlineMethod of inline_object * inline_var * tclass * Type.tparams * tclass_field * tfunc
+	| IOFInlineVar of inline_var
+	| IOFNone
+
+let inline_constructors ctx original_e =
 	let inline_objs = ref IntMap.empty in
 	let inline_objs = ref IntMap.empty in
 	let vars = ref IntMap.empty in
 	let vars = ref IntMap.empty in
 	let scoped_ivs = ref [] in
 	let scoped_ivs = ref [] in
@@ -95,9 +119,9 @@ let inline_constructors ctx e =
 			List.iter (fun iv -> cancel_iv iv p) io.io_aliases;
 			List.iter (fun iv -> cancel_iv iv p) io.io_aliases;
 			PMap.iter (fun _ iv -> cancel_iv iv p) io.io_fields;
 			PMap.iter (fun _ iv -> cancel_iv iv p) io.io_fields;
 			match io.io_kind with
 			match io.io_kind with
-			| IOKCtor(_,isextern,vars) ->
-				List.iter (fun v -> if v.v_id < 0 then cancel_v v p) vars;
-				if isextern then begin
+			| IOKCtor(ioc) ->
+				List.iter (fun v -> if v.v_id < 0 then cancel_v v p) io.io_dependent_vars;
+				if ioc.ioc_forced then begin
 					display_error ctx "Forced inline constructor could not be inlined" io.io_pos;
 					display_error ctx "Forced inline constructor could not be inlined" io.io_pos;
 					display_error ctx (compl_msg "Cancellation happened here") p;
 					display_error ctx (compl_msg "Cancellation happened here") p;
 				end
 				end
@@ -172,25 +196,75 @@ let inline_constructors ctx e =
 		| _ -> mk (TBlock (el)) t p
 		| _ -> mk (TBlock (el)) t p
 	in
 	in
 	let make_expr_for_rev_list (el:texpr list) (t:t) (p:pos) : texpr = make_expr_for_list (List.rev el) t p in
 	let make_expr_for_rev_list (el:texpr list) (t:t) (p:pos) : texpr = make_expr_for_list (List.rev el) t p in
-	let current_io_id = ref 0 in
-	let increment_io_id e = match e.eexpr with
-		| TObjectDecl _ | TArrayDecl _ | TNew _ | (TMeta((Meta.Inline,_,_),{eexpr = TNew _})) -> incr current_io_id
-		| _ -> ()
+	let curr_io_id = ref 0 in
+
+	(*
+		check_for_ctors
+		Returns true if there are any potential inline objects in the expression.
+		It is used to save work before running mark_ctors and analyze_aliases.
+	*)
+	let rec check_for_ctors ?(force_inline=false) e = 
+		let is_ctor, is_meta_inline = match e.eexpr, force_inline with
+			| TMeta((Meta.Inline,_,_),_), _ ->
+				false, true
+			| TObjectDecl _, _
+			| TArrayDecl _, _
+			| TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some ({eexpr = TFunction _})})},_,_), _
+			| TNew _, true ->
+				true, false
+			| _ -> false, false
+		in
+		is_ctor || Type.check_expr (check_for_ctors ~force_inline:is_meta_inline) e
 	in
 	in
+	(*
+		mark_ctors
+		Finds all instances of potential inline objects in an expression and wraps them with metadata @:inlineObject(id).
+		The id is incremented each time and is used later in the final_map phase to identify the correct inline_object.
+	*)
+	let rec mark_ctors ?(force_inline=false) e : texpr =
+		let is_meta_inline = match e.eexpr with (TMeta((Meta.Inline,_,_),e)) -> true | _ -> false in
+		let e = Type.map_expr (mark_ctors ~force_inline:is_meta_inline) e in
+		match e.eexpr, force_inline with
+			| TObjectDecl _, _
+			| TArrayDecl _, _
+			| TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some ({eexpr = TFunction _})})},_,_), _
+			| TNew _, true ->
+				incr curr_io_id;
+				let id_expr = (EConst(Int (string_of_int !curr_io_id)), e.epos) in
+				let meta = (Meta.InlineObject, [id_expr], e.epos) in
+				mk (TMeta(meta, e)) e.etype e.epos
+			| _ -> e
+	in
+
+	(*
+		analyze_aliases is the main work-horse of the constructor inliner analysis.
+		It runs recursively over all expressions, it must do so in code execution order.
+		The expression being analyzed should have been processed with mark_ctors before hand.
+
+		returns: Some(inline variable) if the expression being analyzed returns an inline variable. None otherwise.
+		
+		seen_ctors: used to avoid infinite constructor inlining loops.
+
+		captured: Wether the caller is ready to accept an inline variable. If analysis results in an inline
+		variable and this argument is false then the inline variable must be cancelled before returning.
+
+		is_lvalue: Wether the expression being analyzed is on the left side of an assignment.
+
+		e: The expression to analyze
+	*)
 	let rec analyze_aliases (seen_ctors:tclass_field list) (captured:bool) (is_lvalue:bool) (e:texpr) : inline_var option =
 	let rec analyze_aliases (seen_ctors:tclass_field list) (captured:bool) (is_lvalue:bool) (e:texpr) : inline_var option =
-		increment_io_id e;
 		let mk_io ?(has_untyped=false) (iok : inline_object_kind) (id:int) (expr:texpr) : inline_object =
 		let mk_io ?(has_untyped=false) (iok : inline_object_kind) (id:int) (expr:texpr) : inline_object =
 			let io = {
 			let io = {
 				io_kind = iok;
 				io_kind = iok;
 				io_expr = expr;
 				io_expr = expr;
-				io_pos = e.epos;
+				io_pos = expr.epos;
 				io_cancelled = false;
 				io_cancelled = false;
 				io_declared = false;
 				io_declared = false;
 				io_fields = PMap.empty;
 				io_fields = PMap.empty;
 				io_aliases = [];
 				io_aliases = [];
-				io_id_start = id;
-				io_id_end = id;
 				io_has_untyped = has_untyped;
 				io_has_untyped = has_untyped;
+				io_inline_methods = [];
+				io_dependent_vars = [];
 			} in
 			} in
 			inline_objs := IntMap.add id io !inline_objs;
 			inline_objs := IntMap.add id io !inline_objs;
 			io
 			io
@@ -198,117 +272,167 @@ let inline_constructors ctx e =
 		let analyze_aliases_in_lvalue e = analyze_aliases seen_ctors captured true e in
 		let analyze_aliases_in_lvalue e = analyze_aliases seen_ctors captured true e in
 		let analyze_aliases_in_ctor cf captured e = analyze_aliases (cf::seen_ctors) captured false e in
 		let analyze_aliases_in_ctor cf captured e = analyze_aliases (cf::seen_ctors) captured false e in
 		let analyze_aliases captured e = analyze_aliases seen_ctors captured false e in
 		let analyze_aliases captured e = analyze_aliases seen_ctors captured false e in
-		let handle_field_case te fname validate_io =
-			begin match analyze_aliases true te with
-			| Some({iv_state = IVSAliasing io} as iv) when validate_io io ->
+		let get_io_inline_method io fname = 
+			begin match io.io_kind with
+			| IOKCtor(ctor) ->
 				begin try
 				begin try
-					let fiv = get_io_field io fname in
-					if not (type_iseq_strict fiv.iv_var.v_type e.etype) then raise Not_found;
-					let iv_is_const iv = match iv.iv_kind with IVKField(_,_,Some(_)) -> true | _ -> false in
-					if is_lvalue && iv_is_const fiv then raise Not_found;
-					if fiv.iv_closed then raise Not_found;
-					if not captured || (not is_lvalue && fiv.iv_state == IVSUnassigned) then cancel_iv fiv e.epos;
-					Some(fiv)
-				with Not_found ->
-					cancel_iv iv e.epos;
-					None
+					let f = PMap.find fname ctor.ioc_class.cl_fields in
+					begin match f.cf_params, f.cf_kind, f.cf_expr with
+					| [], Method MethInline, Some({eexpr = TFunction tf}) -> Some (ctor.ioc_class, ctor.ioc_tparams, f, tf)
+					| _ -> None
+					end
+				with Not_found -> None
 				end
 				end
-			| Some(iv) ->
-				cancel_iv iv e.epos;
-				None
 			| _ -> None
 			| _ -> None
 			end
 			end
 		in
 		in
-		match e.eexpr, e.etype with
-		| TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some ({eexpr = TFunction tf})} as cf)} as c,tl,pl),_
-		| TMeta((Meta.Inline,_,_),{eexpr = TNew({ cl_constructor = Some ({cf_expr = Some ({eexpr = TFunction tf})} as cf)} as c,tl,pl)}),_
-			when captured && not (List.memq cf seen_ctors) ->
-			begin
-				let io_id = !current_io_id in
-				let rec loop (vs, decls, es) el = match el with
-					| e :: el ->
-						begin match e.eexpr with
-						| TConst _ -> loop (vs, decls, e::es) el
-						| _ ->
-							let v = alloc_var VGenerated "arg" e.etype e.epos in
-							let decle = mk (TVar(v, Some e)) ctx.t.tvoid e.epos in
-							let io_id_start = !current_io_id in
-							ignore(analyze_aliases true decle);
-							let mde = (Meta.InlineConstructorArgument (v.v_id, io_id_start)), [], e.epos in
-							let e = mk (TMeta(mde, e)) e.etype e.epos in
-							loop (v::vs, decle::decls, e::es) el
-						end
-					| [] -> vs, (List.rev decls), (List.rev es)
-				in
-				let argvs, argvdecls, pl = loop ([],[],[]) pl in
-				let _, cname = c.cl_path in
-				let v = alloc_var VGenerated ("inl"^cname) e.etype e.epos in
-				match Inline.type_inline_ctor ctx c cf tf (mk (TLocal v) (TInst (c,tl)) e.epos) pl e.epos with
-				| Some inlined_expr ->
-					let has_untyped = (Meta.has Meta.HasUntyped cf.cf_meta) in
-					let forced = is_extern_ctor c cf || (match e.eexpr with TMeta _ -> true | _ -> false) in
-					let io = mk_io (IOKCtor(cf,forced,argvs)) io_id inlined_expr ~has_untyped:has_untyped in
-					let rec loop (c:tclass) (tl:t list) =
-						let apply = apply_params c.cl_params tl in
-						List.iter (fun cf ->
-							match cf.cf_kind,cf.cf_expr with
-							| Var _, _ ->
-								let fieldt = apply cf.cf_type in
-								ignore(alloc_io_field io cf.cf_name fieldt v.v_pos);
-							| _ -> ()
-						) c.cl_ordered_fields;
-						match c.cl_super with
-						| Some (c,tl) -> loop c (List.map apply tl)
-						| None -> ()
-					in loop c tl;
-					let iv = add v IVKLocal in
-					set_iv_alias iv io;
-					io.io_id_start <- !current_io_id;
-					ignore(analyze_aliases_in_ctor cf true io.io_expr);
-					io.io_id_end <- !current_io_id;
-					Some iv
-				| _ ->
-					List.iter (fun v -> cancel_v v v.v_pos) argvs;
-					if is_extern_ctor c cf then display_error ctx "Extern constructor could not be inlined" e.epos;
-					None
+		let handle_field_case ?(captured=false) ?(is_lvalue=false) efield ethis fname validate_io : inline_object_field =
+			begin match analyze_aliases true ethis with
+			| Some({iv_state = IVSAliasing io} as iv) when validate_io io ->
+				begin match get_io_inline_method io fname with
+				| Some(c, tl, cf, tf)->
+					let method_type = apply_params c.cl_params tl cf.cf_type in
+					let field_is_function = match efield.etype with | TFun _  -> true	| _ -> false in
+					if field_is_function && Type.does_unify method_type efield.etype then
+						IOFInlineMethod(io,iv,c,tl,cf,tf)
+					else begin
+						cancel_iv iv efield.epos;
+						IOFNone
+					end
+				| None ->
+					begin try
+						let fiv = get_io_field io fname in
+						if not (type_iseq_strict fiv.iv_var.v_type efield.etype) then raise Not_found;
+						let iv_is_const iv = match iv.iv_kind with IVKField(_,_,Some(_)) -> true | _ -> false in
+						if is_lvalue && iv_is_const fiv then raise Not_found;
+						if fiv.iv_closed then raise Not_found;
+						if not captured || (not is_lvalue && fiv.iv_state == IVSUnassigned) then cancel_iv fiv efield.epos;
+						IOFInlineVar(fiv)
+					with Not_found ->
+						cancel_iv iv efield.epos;
+						IOFNone
+					end
+				end
+			| Some(iv) ->
+				cancel_iv iv efield.epos;
+				IOFNone
+			| _ ->
+				IOFNone
 			end
 			end
-		| TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some _} as cf)} as c,_,pl),_ when is_extern_ctor c cf ->
-			error "Extern constructor could not be inlined" e.epos;
-		| TObjectDecl fl, _ when captured && fl <> [] && List.for_all (fun((s,_,_),_) -> Lexer.is_valid_identifier s) fl ->
-			let v = alloc_var VGenerated "inlobj" e.etype e.epos in
-			let ev = mk (TLocal v) v.v_type e.epos in
-			let el = List.map (fun ((s,_,_),e) ->
-				let ef = mk (TField(ev,FDynamic s)) e.etype e.epos in
-				let e = mk (TBinop(OpAssign,ef,e)) e.etype e.epos in
-				e
-			) fl in
-			let io_expr = make_expr_for_list el ctx.t.tvoid e.epos in
-			let io = mk_io (IOKStructure) !current_io_id io_expr in
-			List.iter (fun ((s,_,_),e) -> ignore(alloc_io_field io s e.etype v.v_pos)) fl;
-			let iv = add v IVKLocal in
-			set_iv_alias iv io;
-			List.iter (fun e -> ignore(analyze_aliases true e)) el;
-			io.io_id_end <- !current_io_id;
-			Some iv
-		| TArrayDecl el, TInst(_, [elemtype]) when captured ->
-			let len = List.length el in
-			let v = alloc_var VGenerated "inlarr" e.etype e.epos in
-			let ev = mk (TLocal v) v.v_type e.epos in
-			let el = List.mapi (fun i e ->
-				let ef = mk (TArray(ev,(mk (TConst(TInt (Int32.of_int i))) e.etype e.epos))) elemtype e.epos in
-				mk (TBinop(OpAssign,ef,e)) elemtype e.epos
-			) el in
-			let io_expr = make_expr_for_list el ctx.t.tvoid e.epos in
-			let io = mk_io (IOKArray(len)) !current_io_id io_expr in
-			ignore(alloc_const_io_field io "length" (mk (TConst(TInt (Int32.of_int len))) ctx.t.tint e.epos));
-			for i = 0 to len-1 do ignore(alloc_io_field io (int_field_name i) elemtype v.v_pos) done;
-			let iv = add v IVKLocal in
-			set_iv_alias iv io;
-			List.iter (fun e -> ignore(analyze_aliases true e)) el;
-			io.io_id_end <- !current_io_id;
-			Some iv
-		| TVar(v,None),_ -> ignore(add v IVKLocal); None
-		| TVar(v,Some rve),_ ->
+		in
+		let handle_field_case_no_methods efield ethis fname validate_io = match handle_field_case ~captured:captured ~is_lvalue:is_lvalue efield ethis fname validate_io with
+			| IOFInlineMethod(io,_,_,_,_,_) -> cancel_io io efield.epos; None
+			| IOFInlineVar(iv) -> Some(iv)
+			| IOFNone -> None
+		in
+		let handle_default_case e =
+			let old = !scoped_ivs in
+			scoped_ivs := [];
+			let f e = ignore(analyze_aliases false e) in
+			Type.iter f e;
+			List.iter (fun iv -> iv.iv_closed <- true) !scoped_ivs;
+			scoped_ivs := old;
+			None
+		in
+		let analyze_call_args call_args =
+			let rec loop (vs, es) el = match el with
+				| e :: el ->
+					begin match e.eexpr with
+					| TConst _ -> loop (vs, e::es) el
+					| _ ->
+						let v = alloc_var VGenerated "arg" e.etype e.epos in
+						let decle = mk (TVar(v, Some e)) ctx.t.tvoid e.epos in
+						ignore(analyze_aliases true decle);
+						let mde = (Meta.InlineConstructorArgument (v.v_id, 0)), [], e.epos in
+						let e = mk (TMeta(mde, e)) e.etype e.epos in
+						loop (v::vs, e::es) el
+					end
+				| [] -> vs, (List.rev es)
+			in
+			loop ([],[]) call_args
+		in
+		let handle_inline_object_case (io_id:int) (force_inline:bool) (e:texpr) =
+			match e.eexpr, e.etype with
+			| TNew({ cl_constructor = Some ({cf_expr = Some ({eexpr = TFunction tf})} as cf)} as c,tl,pl),_
+				when captured && not (List.memq cf seen_ctors) ->
+				begin
+					let argvs, pl = analyze_call_args pl in
+					let _, cname = c.cl_path in
+					let v = alloc_var VGenerated ("inl"^cname) e.etype e.epos in
+					match Inline.type_inline_ctor ctx c cf tf (mk (TLocal v) (TInst (c,tl)) e.epos) pl e.epos with
+					| Some inlined_expr ->
+						let inlined_expr = mark_ctors inlined_expr in
+						let has_untyped = (Meta.has Meta.HasUntyped cf.cf_meta) in
+						let forced = is_extern_ctor c cf || force_inline in
+						let io = mk_io (IOKCtor{ioc_class=c; ioc_tparams=tl; ioc_field=cf; ioc_forced=forced}) io_id inlined_expr ~has_untyped:has_untyped in
+						io.io_dependent_vars <- argvs;
+						let rec loop (c:tclass) (tl:t list) =
+							let apply = apply_params c.cl_params tl in
+							List.iter (fun cf ->
+								match cf.cf_kind,cf.cf_expr with
+								| Var _, _ ->
+									let fieldt = apply cf.cf_type in
+									ignore(alloc_io_field io cf.cf_name fieldt v.v_pos);
+								| _ -> ()
+							) c.cl_ordered_fields;
+							match c.cl_super with
+							| Some (c,tl) -> loop c (List.map apply tl)
+							| None -> ()
+						in loop c tl;
+						let iv = add v IVKLocal in
+						set_iv_alias iv io;
+						ignore(analyze_aliases_in_ctor cf true io.io_expr);
+						Some iv
+					| _ ->
+						List.iter (fun v -> cancel_v v v.v_pos) argvs;
+						if is_extern_ctor c cf then display_error ctx "Extern constructor could not be inlined" e.epos;
+						None
+				end
+			| TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some _} as cf)} as c,_,pl),_ when is_extern_ctor c cf ->
+				error "Extern constructor could not be inlined" e.epos;
+			| TObjectDecl fl, _ when captured && fl <> [] && List.for_all (fun((s,_,_),_) -> Lexer.is_valid_identifier s) fl ->
+				let v = alloc_var VGenerated "inlobj" e.etype e.epos in
+				let ev = mk (TLocal v) v.v_type e.epos in
+				let el = List.map (fun ((s,_,_),e) ->
+					let ef = mk (TField(ev,FDynamic s)) e.etype e.epos in
+					let e = mk (TBinop(OpAssign,ef,e)) e.etype e.epos in
+					e
+				) fl in
+				let io_expr = make_expr_for_list el ctx.t.tvoid e.epos in
+				let io = mk_io (IOKStructure) io_id io_expr in
+				List.iter (fun ((s,_,_),e) -> ignore(alloc_io_field io s e.etype v.v_pos)) fl;
+				let iv = add v IVKLocal in
+				set_iv_alias iv io;
+				List.iter (fun e -> ignore(analyze_aliases true e)) el;
+				Some iv
+			| TArrayDecl el, TInst(_, [elemtype]) when captured ->
+				let len = List.length el in
+				let v = alloc_var VGenerated "inlarr" e.etype e.epos in
+				let ev = mk (TLocal v) v.v_type e.epos in
+				let el = List.mapi (fun i e ->
+					let ef = mk (TArray(ev,(mk (TConst(TInt (Int32.of_int i))) e.etype e.epos))) elemtype e.epos in
+					mk (TBinop(OpAssign,ef,e)) elemtype e.epos
+				) el in
+				let io_expr = make_expr_for_list el ctx.t.tvoid e.epos in
+				let io = mk_io (IOKArray(len)) io_id io_expr in
+				ignore(alloc_const_io_field io "length" (mk (TConst(TInt (Int32.of_int len))) ctx.t.tint e.epos));
+				for i = 0 to len-1 do ignore(alloc_io_field io (int_field_name i) elemtype v.v_pos) done;
+				let iv = add v IVKLocal in
+				set_iv_alias iv io;
+				List.iter (fun e -> ignore(analyze_aliases true e)) el;
+				Some iv
+			| _ ->
+				handle_default_case e
+		in
+		match e.eexpr with
+		| TMeta((Meta.Inline,_,_),{eexpr = TMeta((Meta.InlineObject, [(EConst(Int (id_str)), _)], _), e)}) ->
+			let io_id = int_of_string id_str in
+			handle_inline_object_case io_id true e
+		| TMeta((Meta.InlineObject, [(EConst(Int (id_str)), _)], _), e) ->
+			let io_id = int_of_string id_str in
+			handle_inline_object_case io_id false e
+		| TVar(v,None) -> ignore(add v IVKLocal); None
+		| TVar(v,Some rve) ->
 			begin match analyze_aliases true rve with
 			begin match analyze_aliases true rve with
 			| Some({iv_state = IVSAliasing(io)}) ->
 			| Some({iv_state = IVSAliasing(io)}) ->
 				let iv = add v IVKLocal in
 				let iv = add v IVKLocal in
@@ -316,7 +440,7 @@ let inline_constructors ctx e =
 			| _ -> ()
 			| _ -> ()
 			end;
 			end;
 			None
 			None
-		| TBinop(OpAssign, lve, rve),_ ->
+		| TBinop(OpAssign, lve, rve) ->
 			begin match analyze_aliases_in_lvalue lve with
 			begin match analyze_aliases_in_lvalue lve with
 			| Some({iv_state = IVSUnassigned} as iv) ->
 			| Some({iv_state = IVSUnassigned} as iv) ->
 				begin match analyze_aliases true rve with
 				begin match analyze_aliases true rve with
@@ -329,41 +453,64 @@ let inline_constructors ctx e =
 			| Some(iv) -> cancel_iv iv e.epos; ignore(analyze_aliases false rve); None
 			| Some(iv) -> cancel_iv iv e.epos; ignore(analyze_aliases false rve); None
 			| _ -> ignore(analyze_aliases false rve); None
 			| _ -> ignore(analyze_aliases false rve); None
 			end
 			end
-		| TField(te, fa),_ ->
-			handle_field_case te (field_name fa) (fun _ -> true)
-		| TArray(te,{eexpr = TConst (TInt i)}),_ ->
+		| TField(ethis, fa) ->
+			handle_field_case_no_methods e ethis (field_name fa) (fun _ -> true)
+		| TArray(ethis,{eexpr = TConst (TInt i)}) ->
 			let i = Int32.to_int i in
 			let i = Int32.to_int i in
 			let validate_io io = match io.io_kind with IOKArray(l) when i >= 0 && i < l -> true | _ -> false in
 			let validate_io io = match io.io_kind with IOKArray(l) when i >= 0 && i < l -> true | _ -> false in
-			handle_field_case te (int_field_name i) validate_io
-		| TLocal(v),_ when v.v_id < 0 ->
+			handle_field_case_no_methods e ethis (int_field_name i) validate_io
+		| TLocal(v) when v.v_id < 0 ->
 			let iv = get_iv v.v_id in
 			let iv = get_iv v.v_id in
 			if iv.iv_closed || not captured then cancel_iv iv e.epos;
 			if iv.iv_closed || not captured then cancel_iv iv e.epos;
 			Some iv
 			Some iv
-		| TBlock(el),_ ->
+		| TBlock(el) ->
 			let rec loop = function
 			let rec loop = function
 				| [e] -> analyze_aliases captured e
 				| [e] -> analyze_aliases captured e
 				| e::el -> ignore(analyze_aliases true e); loop (el)
 				| e::el -> ignore(analyze_aliases true e); loop (el)
 				| [] -> None
 				| [] -> None
 			in loop el
 			in loop el
-		| TMeta((Meta.InlineConstructorArgument (vid,_),_,_),_),_ ->
+		| TMeta((Meta.InlineConstructorArgument (vid,_),_,_),_) ->
+			(* The contents have already been analyzed, so we must skip the wrapped expression *)
 			(try
 			(try
 				let iv = get_iv vid in
 				let iv = get_iv vid in
 				if iv.iv_closed || not captured then cancel_iv iv e.epos;
 				if iv.iv_closed || not captured then cancel_iv iv e.epos;
 				Some(get_iv vid)
 				Some(get_iv vid)
 			with Not_found -> None)
 			with Not_found -> None)
-		| TParenthesis e,_ | TMeta(_,e),_ | TCast(e,None),_ ->
+		| TParenthesis e | TMeta(_,e) | TCast(e,None) ->
 			analyze_aliases captured e
 			analyze_aliases captured e
-		| _,_ ->
-			let old = !scoped_ivs in
-			scoped_ivs := [];
-			let f e = ignore(analyze_aliases false e) in
-			Type.iter f e;
-			List.iter (fun iv -> iv.iv_closed <- true) !scoped_ivs;
-			scoped_ivs := old;
-			None
+		| TCall(({eexpr=TField(ethis,fa)} as efield),call_args) ->
+			let fname = field_name fa in
+			let fiv = handle_field_case efield ethis fname (fun _ -> true) in
+			begin match fiv with 
+			| IOFInlineMethod(io,io_var,c,tl,cf,tf) ->
+				let argvs, pl = analyze_call_args call_args in
+				io.io_dependent_vars <- io.io_dependent_vars @ argvs;
+				io.io_has_untyped <- io.io_has_untyped or (Meta.has Meta.HasUntyped cf.cf_meta);
+				begin match Inline.type_inline ctx cf tf (mk (TLocal io_var.iv_var) (TInst (c,tl)) e.epos) pl e.etype None e.epos true with
+				| Some e ->
+					let e = mark_ctors e in
+					io.io_inline_methods <- io.io_inline_methods @ [e];
+					begin match analyze_aliases captured e with
+						| Some(iv) ->
+							io.io_dependent_vars <- iv.iv_var :: io.io_dependent_vars;
+							Some(iv)
+						| None -> None
+					end
+				| None ->
+					cancel_io io e.epos;
+					None
+				end
+			| IOFInlineVar(iv) ->
+				cancel_iv iv e.epos;
+				List.iter (fun ca -> ignore(analyze_aliases false ca)) call_args;
+				None
+			| IOFNone ->
+				List.iter (fun ca -> ignore(analyze_aliases false ca)) call_args;
+				None
+			end
+		| _ ->
+			handle_default_case e
 	in
 	in
-	ignore(analyze_aliases [] false false e);
-	current_io_id := 0;
 	let rec get_iv_var_decls (iv:inline_var) : texpr list =
 	let rec get_iv_var_decls (iv:inline_var) : texpr list =
 		match iv with
 		match iv with
 		| {iv_state = IVSAliasing io} -> get_io_var_decls io
 		| {iv_state = IVSAliasing io} -> get_io_var_decls io
@@ -380,7 +527,6 @@ let inline_constructors ctx e =
 	in
 	in
 	let included_untyped = ref false in
 	let included_untyped = ref false in
 	let rec final_map ?(unwrap_block = false) (e:texpr) : ((texpr list) * (inline_object option)) =
 	let rec final_map ?(unwrap_block = false) (e:texpr) : ((texpr list) * (inline_object option)) =
-		increment_io_id e;
 		let default_case e =
 		let default_case e =
 			let f e =
 			let f e =
 				let (el,_) = final_map e in
 				let (el,_) = final_map e in
@@ -388,22 +534,49 @@ let inline_constructors ctx e =
 			in
 			in
 			([Type.map_expr f e], None)
 			([Type.map_expr f e], None)
 		in
 		in
+		(*
+			field_case handles the final map of TField expressions.
+			The last bool in the returned tuple indicates that the field was handled as an inlined method.
+		*)
+		let field_case ethis fa efield : ((texpr list) * (inline_object option) * bool) =
+			let (tel, thiso) = final_map ethis in
+			begin match thiso with
+			| Some io ->
+				let fname = field_name fa in
+				begin try match get_io_field io fname with
+				| {iv_state = IVSAliasing io} ->
+					tel, Some io, false
+				| iv ->
+					let newexpr = match iv.iv_kind with
+						| IVKField(_,_,Some constexpr) -> {constexpr with epos = e.epos}
+						| _ -> mk (TLocal iv.iv_var) efield.etype efield.epos
+					in
+					(newexpr::tel), None, false
+				with Not_found ->
+					(* Since the field is not an inline variable then it must be an inlined method call *)
+					match io.io_inline_methods with
+					| e::el ->
+						(* method fields will appear in the same order as they did during analysis, so we consume the first and remove it from the list *)
+						io.io_inline_methods <- el;
+						let el, io = final_map e in
+						el @ tel, io, true
+					| _ -> die "" __LOC__
+				end
+			| None ->
+				let te = make_expr_for_rev_list tel ethis.etype ethis.epos in
+				[mk (TField(te, fa)) efield.etype efield.epos], None, false
+			end
+		in
 		match e.eexpr with
 		match e.eexpr with
-		| TObjectDecl _ | TArrayDecl _ | TNew _ | (TMeta((Meta.Inline,_,_),{eexpr = TNew _})) ->
+		| TMeta((Meta.InlineObject, [(EConst(Int (id_str)), _)], _), e) ->
+			let io_id = int_of_string id_str in
 			begin try
 			begin try
-				let io = get_io !current_io_id in
-				if io.io_cancelled then begin
-					let result = default_case e in
-					current_io_id := io.io_id_end;
-					result
-				end else begin
-					if io.io_has_untyped then included_untyped := true;
-					current_io_id := io.io_id_start;
-					let el,_ = final_map ~unwrap_block:true io.io_expr in
-					let el = el @ get_io_var_decls io in
-					assert (!current_io_id = io.io_id_end);
-					(el,Some io)
-				end
+				let io = get_io io_id in
+				if io.io_cancelled then raise Not_found;
+				if io.io_has_untyped then included_untyped := true;
+				let el,_ = final_map ~unwrap_block:true io.io_expr in
+				let el = el @ get_io_var_decls io in
+				(el,Some io)
 			with Not_found ->
 			with Not_found ->
 				default_case e
 				default_case e
 			end
 			end
@@ -427,27 +600,29 @@ let inline_constructors ctx e =
 					(e::el), None
 					(e::el), None
 				end
 				end
 			end
 			end
-		| TField(te, fa) ->
-			let (tel, thiso) = final_map te in
-			begin match thiso with
-			| Some io ->
-				let fname = field_name fa in
-				begin match get_io_field io fname with
-				| {iv_state = IVSAliasing io} ->
-					tel, Some io
-				| iv ->
-					let newexpr = match iv.iv_kind with
-						| IVKField(_,_,Some constexpr) -> {constexpr with epos = e.epos}
-						| _ -> mk (TLocal iv.iv_var) e.etype e.epos
-					in
-					(newexpr::tel), None
-				end
-			| None ->
-				let te = make_expr_for_rev_list tel te.etype te.epos in
-				[mk (TField(te, fa)) e.etype e.epos], None
+		| TCall(({eexpr=TField(ethis,fa)} as efield),call_args) ->
+			begin match field_case ethis fa efield with
+			| el, io, true -> (* the field was an inlined method *)
+				el, io
+			| el, _, false -> (* the field was a normal field access *)
+				(*
+					This is equivalent to `default_case e`, but field_case already run final_map
+					on the TField expression so we must reuse those results.
+				*)
+				let f e =
+					let (el,_) = final_map e in
+					make_expr_for_rev_list el e.etype e.epos
+				in
+				let e1 = make_expr_for_rev_list el efield.etype efield.epos in
+				let e = {e with eexpr = TCall(e1, List.map f call_args)} in
+				[e], None
 			end
 			end
-		| TArray(te, ({eexpr = TConst (TInt i)} as indexexpr)) ->
-			let (tel, thiso) = final_map te in
+		| TField(ethis, fa) ->
+			let el, io, is_method = field_case ethis fa e in
+			assert(not is_method);
+			el, io
+		| TArray(ethis, ({eexpr = TConst (TInt i)} as indexexpr)) ->
+			let (tel, thiso) = final_map ethis in
 			begin match thiso with
 			begin match thiso with
 			| Some io ->
 			| Some io ->
 				let i = Int32.to_int i in
 				let i = Int32.to_int i in
@@ -460,7 +635,7 @@ let inline_constructors ctx e =
 					(local::tel), None
 					(local::tel), None
 				end
 				end
 			| None ->
 			| None ->
-				let te = make_expr_for_rev_list tel te.etype te.epos in
+				let te = make_expr_for_rev_list tel ethis.etype ethis.epos in
 				[mk (TArray(te, indexexpr)) e.etype e.epos], None
 				[mk (TArray(te, indexexpr)) e.etype e.epos], None
 			end
 			end
 		| TLocal v when v.v_id < 0 ->
 		| TLocal v when v.v_id < 0 ->
@@ -483,12 +658,8 @@ let inline_constructors ctx e =
 			let el, io = loop [] el in
 			let el, io = loop [] el in
 			let el = if unwrap_block || Option.is_some io then el else [mk (TBlock (List.rev el)) e.etype e.epos] in
 			let el = if unwrap_block || Option.is_some io then el else [mk (TBlock (List.rev el)) e.etype e.epos] in
 			el, io
 			el, io
-		| TMeta((Meta.InlineConstructorArgument (_,io_id_start),_,_),e) ->
-			let old_io_id = !current_io_id in
-			current_io_id := io_id_start;
-			let result = final_map e in
-			current_io_id := old_io_id;
-			result
+		| TMeta((Meta.InlineConstructorArgument (_,_),_,_),e) ->
+			final_map e
 		| TParenthesis e' | TCast(e',None) | TMeta(_,e') ->
 		| TParenthesis e' | TCast(e',None) | TMeta(_,e') ->
 			let el, io = final_map e' in
 			let el, io = final_map e' in
 			begin match io with
 			begin match io with
@@ -500,9 +671,12 @@ let inline_constructors ctx e =
 			end
 			end
 		| _ -> default_case e
 		| _ -> default_case e
 	in
 	in
+	if not (check_for_ctors original_e) then original_e else
+	let e = mark_ctors original_e in
+	ignore(analyze_aliases [] false false e);
 	if IntMap.for_all (fun _ io -> io.io_cancelled) !inline_objs then begin
 	if IntMap.for_all (fun _ io -> io.io_cancelled) !inline_objs then begin
 		IntMap.iter (fun _ iv -> let v = iv.iv_var in if v.v_id < 0 then v.v_id <- -v.v_id ) !vars;
 		IntMap.iter (fun _ iv -> let v = iv.iv_var in if v.v_id < 0 then v.v_id <- -v.v_id ) !vars;
-		e
+		original_e
 	end else begin
 	end else begin
 		let el,_ = final_map e in
 		let el,_ = final_map e in
 		let cf = ctx.curfield in
 		let cf = ctx.curfield in

+ 11 - 0
tests/misc/projects/inline-constructors/Extern.hx

@@ -0,0 +1,11 @@
+class Inl {
+	public var i : Int = 0;
+	public extern inline function new() {}
+}
+
+class Extern {
+	public static function main() {
+		var a = new Inl();
+		trace(a);
+	}
+}

+ 11 - 0
tests/misc/projects/inline-constructors/ForceInline.hx

@@ -0,0 +1,11 @@
+class Inl {
+	public var i : Int = 0;
+	public function new() {}
+}
+
+class ForceInline {
+	public static function main() {
+		var a = inline new Inl();
+		trace(a);
+	}
+}

+ 2 - 0
tests/misc/projects/inline-constructors/compile-extern-fail.hxml

@@ -0,0 +1,2 @@
+Extern
+-js extern.js

+ 2 - 0
tests/misc/projects/inline-constructors/compile-extern-fail.hxml.stderr

@@ -0,0 +1,2 @@
+Extern.hx:8: characters 11-20 : Forced inline constructor could not be inlined
+Extern.hx:9: characters 9-10 : Cancellation happened here

+ 2 - 0
tests/misc/projects/inline-constructors/compile-force-inline-fail.hxml

@@ -0,0 +1,2 @@
+ForceInline
+-js force-inline.js

+ 2 - 0
tests/misc/projects/inline-constructors/compile-force-inline-fail.hxml.stderr

@@ -0,0 +1,2 @@
+ForceInline.hx:8: characters 11-27 : Forced inline constructor could not be inlined
+ForceInline.hx:9: characters 9-10 : Cancellation happened here

+ 1 - 0
tests/optimization/run.hxml

@@ -21,5 +21,6 @@
 --macro Macro.register('TestJs')
 --macro Macro.register('TestJs')
 --macro Macro.register('TestLocalDce')
 --macro Macro.register('TestLocalDce')
 --macro Macro.register('TestTreGeneration')
 --macro Macro.register('TestTreGeneration')
+--macro Macro.register('TestInlineConstructors')
 --macro Macro.register('issues')
 --macro Macro.register('issues')
 --dce std
 --dce std

+ 100 - 0
tests/optimization/src/TestInlineConstructors.hx

@@ -0,0 +1,100 @@
+class InlineClass {
+	public var a = 1;
+	public var b = "";
+	public var c = "hello";
+	public inline function new() {
+	}
+
+	public inline function method() {
+		return a + b + c;
+	}
+}
+
+class NestedInlineClass {
+	public var a : InlineClass;
+	public var b : Array<Int>;
+	public var c : {a: Int};
+
+	public inline function new() {
+		a = new InlineClass();
+		b = [1,2,3];
+		c = {a:4};
+	}
+}
+
+class TestInlineConstructors extends TestBase {
+	@:js('return [1,2,3,3];')
+	static function testArrayInlining() {
+		var a = [1,2,3];
+		return [a[0], a[1], a[2], a.length];
+	}
+
+	@:js('return [2,"hello","world"];')
+	static function testAnonymousStructureInlining() {
+		var a = {a: 1, b: "", c: "hello"};
+		a.a = 2;
+		a.b = a.c;
+		a.c = "world";
+		return ([a.a,a.b,a.c] : Array<Dynamic>);
+	}
+
+	@:js('return [2,"hello","world"];')
+	static function testClassConstructorInlining() {
+		var a = new InlineClass();
+		a.a = 2;
+		a.b = a.c;
+		a.c = "world";
+		return return ([a.a,a.b,a.c] : Array<Dynamic>);
+	}
+
+	@:js('return [1,2,4,"test",2,1,4,3,4];')
+	static function testNestedInlining() {
+		var a = {
+			a: new NestedInlineClass(),
+			b: [new NestedInlineClass()],
+			c: {test: new NestedInlineClass()}
+		};
+		a.b[0].a.c = "test";
+		return ([a.a.a.a, a.a.b[1], a.a.c.a, a.b[0].a.c, a.b[0].b[1], a.b.length, a.c.test.c.a, a.c.test.b[2], a.c.test.c.a]:Dynamic);
+	}
+
+	@:js('return [1,"hello","world"];')
+	static function testMultipleAliasingVariables() {
+		var a = new InlineClass();
+		var b = a;
+		var d = [b,a];
+		d[1].b = "hello";
+		a.c = "world";
+		return ([a.a, a.b, d[1].c]:Array<Dynamic>);
+	}
+
+	@:js('return [1,"","hello"];')
+	static function testUnassignedVariables() {
+		var a;
+		a = new InlineClass();
+		return ([a.a, a.b, a.c]:Array<Dynamic>);
+	}
+
+	@:js('return [1,3,"hello"];')
+	static function testNoExplicitVariables() {
+		return ([
+			new InlineClass().a,
+			[1,2,3][2],
+			new NestedInlineClass().a.c
+		]:Array<Dynamic>);
+	}
+
+	@:js('return 1 + "" + "hello";')
+	static function testMethodInlining() {
+		var a : {function method() : String;} = new InlineClass();
+		return a.method();
+	}
+
+	@:js('var b = new InlineClass();return [new InlineClass().method(1),b.method(2)];')
+	static function testIncompatibleMethodCancelling() {
+		// InlineClass.method doesn't take any arguments, the method should not be inlined.
+		var a : {function method(arg : Int) : String;} = cast new InlineClass();
+		var b : Dynamic = new InlineClass();
+		return [a.method(1), b.method(2)];
+	}
+}