[m-dev.] for review: RTTI for closures

Fergus Henderson fjh at cs.mu.OZ.AU
Mon Mar 22 01:03:20 AEDT 1999


On 21-Mar-1999, Zoltan Somogyi <zs at cs.mu.OZ.AU> wrote:
> 
> compiler/code_info.m:
> 	Move the code to handle layouts to continuation_info.m,
> 	since that's where it belongs. Leave only the code for picking
> 	up parameters from code_infos and for putting results back in there.
> 
> 	Remove the redundant arguments of code_into__init, and extract
> 	them from ProcInfo, to make clear that they are related.

s/code_into/code_info/

> compiler/code_gen.m:
> 	Since we pass ProcInfo to code_into__init, don't pass its components.

Likewise.

As well as the files mentioned in the log message,
you also need to modify dl.m (duplicated in browser/dl.m and
extras/dynamic_linking/dl.m), which constructs closures,
and extras/exceptions.m, which contains calls to
do_call_<detism>_closure in hand-coded low-level C code.
 
> --- continuation_info.m	1998/11/24 03:56:59	1.19
> +++ continuation_info.m	1999/03/20 07:29:15
...
> +:- type closure_layout_info
> +	--->	closure_layout_info(
> +			list(closure_arg_info),
> +				% there is one closure_arg_info for each
> +				% argument of the called procedure,
> +				% even the args which are not in the closure
> +			map(tvar, set(layout_locn))
> +				% locations of polymorphic type vars,
> +				% encoded so that rN refers to argument N
> +		).
> +
> +:- type closure_arg_info
> +	--->	closure_arg_info(
> +			type,
> +			(inst)
> +		).

What's the meaning of the `inst' here?
Is that the initial inst before the call?
If so, you should document this.

It might be useful to also include information about the final insts after
the call too, and about the determinism of the procedure.
That would give enough information to allow us to implement run-time
checked dynamic inst casts on higher-order terms, which would be helpful
for dynamic loading.  And this would also be useful information
when printing out higher-order terms, or for when we provide a user-level
interface to this RTTI for closures.

This is not necessarily something that needs to be done right away, of
course, but at least adding a comment along these lines would be a good
idea, IMHO.

>  unify_gen__generate_construction_2(pred_closure_tag(PredId, ProcId),
>  		Var, Args, _Modes, Code) -->
> +	% This code constructs or extends a closure.
...
>  			incr_hp(NewClosure, no,
>  				binop(+, lval(NumOldArgs),
> -				NumNewArgsPlusTwo_Rval), "closure")
> +				NumNewArgsPlusThree_Rval), "closure")
>  				- "allocate new closure",
>  			assign(field(yes(0), lval(NewClosure), Zero),
> +				lval(field(yes(0), OldClosure, Zero)))
> +				- "set closure layout structure",
> +			assign(field(yes(0), lval(NewClosure), One),
> +				lval(field(yes(0), OldClosure, One)))
> +				- "set closure code pointer",
> +			assign(field(yes(0), lval(NewClosure), Two),
>  				binop(+, lval(NumOldArgs), NumNewArgs_Rval))
>  				- "set new number of arguments",
> -			assign(LoopCounter, Zero)
> +			assign(NumOldArgs, binop(+, lval(NumOldArgs), Three))
> +				- "set up loop limit",
> +			assign(LoopCounter, Three)
>  				- "initialize loop counter",
> +			goto(label(LoopTest))
> +				- "enter the loop at the conceptual top",
>  			label(LoopStart) - "start of loop",
> -			assign(LoopCounter,
> -				binop(+, lval(LoopCounter), One))
> -				- "increment loop counter",
>  			assign(field(yes(0), lval(NewClosure),
>  					lval(LoopCounter)),
>  				lval(field(yes(0), OldClosure,
>  					lval(LoopCounter))))
> -				- "copy old field",
> -			if_val(binop(<=, lval(LoopCounter),
> -				lval(NumOldArgs)), label(LoopStart))
> -				- "repeat the loop?",
> -			label(LoopEnd) - "end of loop"
> +				- "copy old hidden argument",
> +			assign(LoopCounter,
> +				binop(+, lval(LoopCounter), One))
> +				- "increment loop counter",
> +			label(LoopTest) - "the test of the loop",
> +			if_val(binop(<, lval(LoopCounter), lval(NumOldArgs)),
> +				label(LoopStart))
> +				- "repeat the loop?"

Any particular reason for changing the loop code for extending closures
in this way? 

This change to the looping code is a change over and above what the log
message says ("switch to creating new style closures, complete with
layout info"), so this change and its rationale should be mentioned
in the log message.

> +		{ globals__lookup_bool_option(Globals, typeinfo_liveness,
> +			TypeInfoLiveness) },
> +		{
> +			TypeInfoLiveness = yes,
> +			continuation_info__generate_closure_layout(
> +				ModuleInfo, PredId, ProcId, ClosureInfo),
> +			MaybeClosureInfo = yes(ClosureInfo)
> +		;
> +			TypeInfoLiveness = no,
> +			% In the absence of typeinfo liveness, procedures
> +			% are not guaranteed to have typeinfos for all the
> +			% type variables in their signatures. Such a missing
> +			% typeinfo would cause a compile-time abort in
> +			% continuation_info__generate_closure_layout,
> +			% and even if that predicate was modified,
> +			% we still couldn't generate a usable layout
> +			% structure.
> +			MaybeClosureInfo = no

Hmmm.  Isn't this going to cause problems, or at least fail to resolve
the current problems, for deep copy in grades for which we don't have
typeinfo liveness?

I think we will need to either (a) always enabled typeinfo liveness
or (b) require typeinfo liveness for any polymorphic procedure whose
address is taken (similar to the way we currently require compact
argument passing for any procedure whose address is taken).

> +	if (num_arg_typeclass_infos < MR_CLASS_METHOD_CALL_INPUTS) {
> +			/* copy to the left, from the left */
...
> +	} else if (num_arg_typeclass_infos > MR_CLASS_METHOD_CALL_INPUTS) {
> +			/* copy to the right, from the right */

These comments in the code for calling type class methods
about "copy to the <dirn>, from the <dirn>" should
be duplicated in the code for calling closures.

> +++ mercury_ho_call.h	Sat Mar 20 20:35:15 1999
> +/*
> +** A closure is a vector of words containing:
> +**
> +**	one word pointing to the closure layout structure of the procedure
> +**	one word pointing to the code of the procedure
> +**	one word giving the number of arguments hidden in the closure (N)
> +**	N words representing the N hidden arguments
...

It would be better to define this as an actual C struct.
For example:

	/*
	** MR_VARIABLE_SIZED is used as the array bounds for
	** variable-sized arrays whose actual bounds will not be
	** determined until the structure containing them is allocated.
	*/
	#if defined(__GNUC__) /* || defined(__BORLANDC__) || ... */
	  #define MR_VARIABLE_SIZED /* nothing */
	#else
	  #define MR_VARIABLE_SIZED 1
	#endif

	typedef struct MR_Closure_struct {
		/* the closure layout structure of the procedure */
		MR_Closure_Layout 	*closure_layout;

		/* the code for the procedure */
		Code			*code_address;

		/* the hidden arguments for the procedure */
		Unsigned		num_hidden_arguments;
		Word			hidden_arguments[MR_VARIABLE_SIZED];
	} MR_Closure;

	/* these compute the size to allocate for an MR_Closure */
	#define MR_closure_size_in_bytes(num_hidden_args)		\
		(offsetof(MR_Closure, hidden_arguments)			\
			+ sizeof(Word) * (num_hidden_args))
	#define MR_closure_size_in_words(num_hidden_args)		\
		(offsetof(MR_Closure, hidden_arguments) / sizeof(Word)  \
			+ (num_hidden_args))

Among other things, defining these kinds of things as C structures makes
it much easier to use a C debugger.

Then instead of

> +#define	MR_CLOSURE_LAYOUT_VECTOR(c)	((Word *) ((c)[0]))
> +#define	MR_CLOSURE_CODEADDR(c)		((Code *) ((c)[1]))
> +#define	MR_CLOSURE_HIDDEN_ARG_COUNT(c)	((Integer) ((c)[2]))
> +#define	MR_CLOSURE_HIDDEN_ARG(c, i)	((Word) ((c)[2 + (i)]))

you could use

	#define	MR_CLOSURE_LAYOUT_VECTOR(c)	((c)->closure_layout)
	#define	MR_CLOSURE_CODEADDR(c)		((c)->code_address)
	#define	MR_CLOSURE_HIDDEN_ARG_COUNT(c)	((c)->num_hidden_arguments)
	#define	MR_CLOSURE_HIDDEN_ARG(c, i)	((c)->hidden_arguments[(i) - 1])

(perhaps also casting the argument `c' to type `(MR_Closure *)',
but it might be better to do that in the callers if needed).

> +** The closure layout structure of a procedure is a vector of words containing
> +**
> +**	a MR_Stack_Layout_Proc_Id structure
> +**	one word giving the number of arguments of the procedure (M)
> +**	M words giving pseudotypeinfos for the arguments
> +**	one word giving the number of type vars in those pseudotypeinfos (T)
> +**	T words giving the locations of the typeinfos for those type vars

Same applies here, although admittedly it is not quite as nice:

	typedef struct MR_Closure_Layout_struct {
		/* the proc_id layout for this procedure */
		MR_Stack_Layout_Proc_Id	proc_id_layout;

		/* the number (M) of arguments of the procedure */
		Unsigned 	num_arguments;

		/* M words giving pseudotypeinfos for the arguments */
		Word		arg_pseudo_type_infos[MR_VARIABLE_SIZED];

		/* ... followed by an MR_Closure_Layout_Part_2 */
	} MR_Closure_Layout;
	typedef struct MR_Closure_Layout_Part_2_struct {
		/* the number (T) of type vars in the argument
		   pseudotypeinfos */
		Unsigned		num_type_vars;

		/* T words giving the locations of the typeinfos for
		   those type vars */
		Word			type_info_locations[MR_VARIABLE_SIZED];
	} MR_Closure_Layout_Part_2;

Then the following macros, which incidentially are in fact buggy, since
they use sizeof(foo) where they should be using sizeof(foo) / sizeof(Word),

> +#define	MR_CLOSURELAYOUT_PROC_ARITY(lv)					\
> +		((lv)[sizeof(MR_Stack_Layout_Proc_Id)])
> +#define	MR_CLOSURELAYOUT_ARG_PSEUDO_TI(lv, i)				\
> +		((lv)[sizeof(MR_Stack_Layout_Proc_Id) + (i)])
> +#define	MR_CLOSURELAYOUT_TYPE_PARAM_COUNT(lv)				\
> +		((lv)[sizeof(MR_Stack_Layout_Proc_Id) + 1 + 		\
> +			MR_CLOSURELAYOUT_PROC_ARITY(lv))
> +#define	MR_CLOSURELAYOUT_TYPE_PARAM_LOCN(lv, p)				\
> +		((lv)[sizeof(MR_Stack_Layout_Proc_Id) + 1 + 		\
> +			MR_CLOSURELAYOUT_PROC_ARITY(lv) + (p)])
> +#define	MR_CLOSURELAYOUT_TYPE_PARAM_LOCN_VEC(lv)			\
> +		&((lv)[sizeof(MR_Stack_Layout_Proc_Id) + 1 +		\
> +			MR_CLOSURELAYOUT_PROC_ARITY(lv))

would instead become

#define	MR_CLOSURE_LAYOUT_PROC_ARITY(lv)				\
		((lv)->num_arguments)
#define	MR_CLOSURE_LAYOUT_ARG_PSEUDO_TI(lv, i)				\
		((lv)->arg_pseudo_type_infos[(i) - 1])
#define	MR_CLOSURE_LAYOUT_PART_2(lv)					\
		((MR_Closure_Layout_Part_2 *)				\
		 &(lv)->arg_pseudo_type_infos[(lv)->num_arguments])
#define	MR_CLOSURE_LAYOUT_TYPE_PARAM_COUNT(lv)				\
		(MR_CLOSURE_LAYOUT_PART_2(lv)->num_type_vars)
#define	MR_CLOSURELAYOUT_TYPE_PARAM_LOCN_VEC(lv)			\
		(MR_CLOSURE_LAYOUT_PART_2(lv)->type_info_locations)
#define	MR_CLOSURELAYOUT_TYPE_PARAM_LOCN(lv, p)				\
		(MR_CLOSURE_LAYOUT_PART_2(lv)->type_info_locations[p])

> +** The reason why T is just before the typeinfo locatio vector and not

s/locatio/location/

> +** The places in the system that know about the layouts of closures are
> +**
> +**	compiler/unify_gen.m (unify_gen__generate_construction_2)
> +**	runtime/mercury_ho_call.[ch]

Current dl.m in

	extras/dynamic_loading/dl.m
	browser/dl.m

also knows about the layouts of closures.

-- 
Fergus Henderson <fjh at cs.mu.oz.au>  |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>  |  of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3        |     -- the last words of T. S. Garp.



More information about the developers mailing list