[m-rev.] for review: first step towards MLDS->C accurate GC
Peter Ross
peter.ross at miscrit.be
Tue Nov 27 22:39:13 AEDT 2001
On Tue, Nov 27, 2001 at 12:14:39AM +1100, Fergus Henderson wrote:
> Branches: main
> Estimated hours taken: 16
>
> Preliminary steps towards support for accurate GC
> in the MLDS->C back-end.
>
> compiler/ml_elim_nested.m:
> Add support for a new pass that puts local
> variables that might contain pointers into
> a struct, and chains these structs together.
>
> compiler/mercury_compile.m:
> Invoke the new pass (if `--gc accurate').
>
> runtime/mercury.h:
> runtime/mercury.c:
> Declare the `stack_chain' global variable that
> points to the head of the chain.
>
> Workspace: /home/earth/fjh/ws-earth3/mercury
> Index: compiler/ml_elim_nested.m
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/compiler/ml_elim_nested.m,v
> retrieving revision 1.43
> diff -u -d -r1.43 ml_elim_nested.m
> --- compiler/ml_elim_nested.m 24 Oct 2001 05:44:01 -0000 1.43
> +++ compiler/ml_elim_nested.m 26 Nov 2001 13:03:53 -0000
> @@ -8,7 +8,24 @@
> % Main author: fjh
>
> % This module is an MLDS-to-MLDS transformation
> -% that eliminates nested functions.
> +% that has two functions:
> +% (1) eliminating nested functions
> +% (2) putting local variables that might contain pointers into
> +% structs, and chaining these structs together,
> +% for use with accurate garbage collection.
> +%
> +% The two transformations are quite similar,
> +% so they're both handled by the same code;
> +% a flag is passed to say which transformation
> +% should be done.
> +
> +% XXX Would it be possible to do both in a single pass?
> +
> +%-----------------------------------------------------------------------------%
> +%-----------------------------------------------------------------------------%
> +% (1) eliminating nested functions
> +%-----------------------------------------------------------------------------%
> +%-----------------------------------------------------------------------------%
>
> % Note that this module does not attempt to handle arbitrary MLDS
> % as input; it will only work with the output of the current MLDS
> @@ -112,6 +129,163 @@
> % ml_code_gen puts in calls to the nested functions.
>
> %-----------------------------------------------------------------------------%
> +%-----------------------------------------------------------------------------%
> +% (2) accurate GC
> +%-----------------------------------------------------------------------------%
> +%-----------------------------------------------------------------------------%
> +%
> +% SUMMARY
> +%
> +% This is an MLDS-to-MLDS transformation that transforms the
> +% MLDS code to add the information needed to do accurate GC
> +% when compiling to C (or to assembler).
> +%
> +% Basically what we do is to put all all local variables that might
> +% contain pointers in structs, with one struct for each stack frame,
> +% and chain these structs together. At GC time, we traverse the chain
> +% of structs. This allows us to accurately scan the C stack.
> +%
> +%-----------------------------------------------------------------------------%
> +%
> +% DETAILED DESCRIPTION
> +%
> +% For each function, we generate a struct for that function.
> +% Each such struct starts with a sub-struct containing a couple of
> +% fixed fields, which allow the GC to traverse the chain:
> +%
> +% struct <function_name>_pointers {
> +% struct MR_StackChain fixed_fields;
> +% ...
> +% };
> +%
> +% The fixed fields are as follows:
> +%
> +% struct MR_StackChain {
> +% struct MR_StackChain *prev;
> +% void (*traverse_frame)(void *this_frame);
> +% };
> +%
> +% The prev field holds a link to the entry for this function's caller.
> +% The traverse_frame field is the address of a function to
> +% trace everything pointed to by this stack frame.
> +%
> +% To ensure that we don't try to traverse uninitialized fields,
> +% we zero-initialize each struct before inserting it into the chain.
> +%
> +% We need to keep a link to the topmost frame on the stack.
> +% There's two possible ways that we could handle this.
> +% One way is to pass it down as an parameter.
> +% Each function would get an extra parameter `stack_chain' which
> +% points to the caller's struct.
> +% An alternative approach is to just have a global variable
> +% `stack_chain' that points to the top of the stack. We need extra code
> +% to set this pointer when entering and returning from functions.
> +% To make this approach thread-safe, the variable would actually
> +% need to be thread-local rather than global.
> +% This approach would probably work best if the variable is
> +% a GNU C global register variable, which would make it both
> +% efficient and thread-safe.
> +% XXX Currently, for simplicity, we're using a global variable.
> +%
I assume that there are no problems with exceptions. I can't think of
any but I am just making sure that you have thought about it.
> +% As an optimization, we ought to not bother allocating a struct for
> +% functions that don't have any variables that might contain pointers.
> +% We also ought to not bother allocating a struct for leaf functions that
> +% don't contain any functions calls or memory allocations.
> +% XXX These optimizations are not yet implemented!
> +%
> +%-----------------------------------------------------------------------------%
> +%
> +% EXAMPLE
> +%
> +% If we have a function
> +%
> +% RetType
> +% foo(Arg1Type arg1, Arg2Type arg2, ...)
> +% {
> +% Local1Type local1;
> +% Local1Type local2;
> +% ...
> +% local1 = MR_new_object(...);
> +% ...
> +% bar(arg1, arg2, local1, &local2);
> +% ...
> +% }
> +%
> +% where say Arg1Type and Local1Type might contain pointers,
> +% but Arg2Type and Local2Type don't, then we would transform it as follows:
> +%
> +% struct foo_frame {
> +% MR_StackChain fixed_fields;
> +% Arg1Type arg1;
> +% Local1Type arg1;
> +% ...
> +% };
> +%
Here you call this foo_frame but according to the previous naming scheme
it is foo_pointers.
> +% static void
> +% foo_traverse_frame(void *this_frame) {
> +% struct foo_frame *frame = (struct foo_frame *)this_frame;
> +% MR_GC_TRAVERSE(<TypeInfo for type of arg1>, &frame->arg1);
> +% MR_GC_TRAVERSE(<TypeInfo for type of local1>, &frame->local1);
> +% ...
> +% }
> +%
> +% RetType
> +% foo(Arg1Type arg1, Arg2Type arg2, ...)
> +% {
> +% struct foo_frame this_frame;
> +% Local1Type local2;
> +%
> +% this_frame.fixed_fields.prev = stack_chain;
> +% this_frame.fixed_fields.traverse_frame = foo_traverse_frame;
> +% this_frame.fixed_fields.arg1 = arg1;
> +% stack_chain = &this_frame;
> +%
> +% ...
> +% local1 = MR_new_object(...);
> +% ...
> +% bar(this_frame.arg1, arg2, this_frame.local1, &local2);
> +% ...
> +% stack_chain = stack_chain->prev;
> +% }
> +%
Shouldn't there be a this_frame.local1 = NULL;
and you have forgotten the this_frame qualifier for the result of the
MR_new_object.
> +% Alternatively, if we were passing stack_chain as an argument,
> +% rather than treating it as a global variable, then the generated
> +% code for foo() would look like this:
> +%
> +% RetType
> +% foo(struct MR_StackChain *stack_chain,
> +% Arg1Type arg1, Arg2Type arg2, ...)
> +% {
> +% struct foo_frame this_frame;
> +% Local1Type local2;
> +%
> +% this_frame.fixed_fields.prev = stack_chain;
> +% this_frame.fixed_fields.traverse_frame = foo_traverse_frame;
> +% this_frame.fixed_fields.arg1 = arg1;
> +%
> +% ...
> +% local1 = MR_new_object(&this_frame, ...);
> +% ...
> +% bar(&this_frame, this_frame.arg1, arg2,
> +% this_frame.local1, &local2);
> +% ...
> +% /* no need to explicitly unchain the stack frame here */
> +% }
> +%
Same problems.
> +% The code in the Mercury runtime to traverse the stack frames would
> +% look something like this:
> +%
> +% void
> +% MR_traverse_stack(struct MR_StackChain *stack_chain)
> +% {
> +% while (stack_chain != NULL) {
> +% (*stack_chain->traverse)(stack_chain);
> +% stack_chain = stack_chain->prev;
> +% }
> +% }
> +%
> @@ -238,47 +433,85 @@
> % really a big problem, since the code
> % that generates these arguments needs them.
> %
> - ( InsertedEnv = yes ->
> + (
> + Action = hoist_nested_funcs,
> + InsertedEnv = no
> + ->
> + FuncBody = FuncBody1,
> + HoistedDefns = HoistedDefns0
> + ;
> %
> % If the function's arguments are
> - % referenced by nested functions, then
> - % we need to copy them to local
> + % referenced by nested functions,
> + % or (for accurate GC) may contain pointers,
> + % then we need to copy them to local
> % variables in the environment
> % structure.
> %
> ml_maybe_copy_args(Arguments, FuncBody0,
> - ModuleName, EnvTypeName, EnvPtrTypeName,
> + ElimInfo, EnvTypeName, EnvPtrTypeName,
> Context, _ArgsToCopy, CodeToCopyArgs),
>
Will this code have problems because it is possibly executed twice?
This could happen on the hoist_nested_funcs pass when InsertedEnv = yes
and then it will always get called on the chain_gc_stack_frames pass.
Speaking of which does it matter what order you call these passes in?
Wait I think I can see what happens, but you say that for both passes you
"need to copy them to local variables in the environment structure".
However this `environment structure' is different for each pass, on one pass
it is the environment structure and on the other pass the stack frame.
I think you need to make this point more clear and you probably need to
rename variables to reflect that there is more than one possible
`environment structure'.
> %
> + % Insert code to unlink this stack frame
> + % before doing any tail calls or returning
> + % from the function, either explicitly
> + % or implicitly.
> + %
> + % add unlink statements before
> + % any explicit returns or tail calls
> + ( Action = chain_gc_stack_frames ->
> + add_unchain_stack_to_statement(
> + FuncBody1, FuncBody2,
> + ElimInfo, _ElimInfo)
> + ;
> + FuncBody2 = FuncBody1
> + ),
> + % add a final unlink statement
> + % at the end of the function,
> + % if needed. This is only needed if
> + % the function has no return values --
> + % if there is a return value, then the
> + % function must exit with an explicit
> + % return statement.
> + (
> + Action = chain_gc_stack_frames,
> + RetValues = []
> + ->
> + UnchainFrame = [ml_gen_unchain_frame(
> + Context, ElimInfo)]
> + ;
> + UnchainFrame = []
> + ),
> +
> + %
> % insert the definition and
> % initialization of the environment
> % struct variable at the start of the
> - % top-level function's body
> + % top-level function's body,
> + % and append the final unlink statement
> + % (if any) at the end
> %
> FuncBody = ml_block(EnvDecls,
> - list__append(
> - [InitEnv | CodeToCopyArgs],
> - [FuncBody1]), Context),
> + InitEnv ++ CodeToCopyArgs ++
> + [FuncBody2] ++ UnchainFrame,
> + Context),
> %
> - % insert the environment struct type
> - % at the start of the list of hoisted definitions
> + % insert the environment struct type at
> + % the start of the list of hoisted definitions
> % (preceding the previously nested functions
> % and static constants in HoistedDefns0),
> %
> HoistedDefns = [EnvTypeDefn | HoistedDefns0]
> - ;
> - FuncBody = FuncBody1,
> - HoistedDefns = HoistedDefns0
> )
> ),
> DefnBody = mlds__function(PredProcId, Params,
> defined_here(FuncBody), Attributes),
> Defn = mlds__defn(Name, Context, Flags, DefnBody),
> - FlatDefns = list__append(HoistedDefns, [Defn])
> + Defns = list__append(HoistedDefns, [Defn])
> ;
> % leave definitions of things other than functions unchanged
> - FlatDefns = [Defn0]
> + Defns = [Defn0]
> ).
>
> %
> @@ -361,17 +598,20 @@
> % struct <EnvClassName> *env_ptr;
> % env_ptr = &env;
> %
> -:- pred ml_create_env(mlds__class_name, list(mlds__defn), mlds__context,
> +:- pred ml_create_env(action, mlds__class_name, list(mlds__defn), mlds__context,
> mlds_module_name, globals, mlds__defn, mlds__type,
> - list(mlds__defn), mlds__statement).
> -:- mode ml_create_env(in, in, in, in, in, out, out, out, out) is det.
> + list(mlds__defn), list(mlds__statement)).
> +:- mode ml_create_env(in, in, in, in, in, in, out, out, out, out) is det.
>
> -ml_create_env(EnvClassName, LocalVars, Context, ModuleName, Globals,
> +ml_create_env(Action, EnvClassName, LocalVars, Context, ModuleName, Globals,
> EnvTypeDefn, EnvTypeName, EnvDecls, InitEnv) :-
> %
> % generate the following type:
> %
> % struct <EnvClassName> {
> + % #ifdef ACCURATE_GC
> + % struct MR_StackChain fixed_fields;
> + % #endif
> % <LocalVars>
> % };
> %
A quick comment that the #ifdef is a virtual preprocessor statement in
that the compiler does it.
> @@ -390,7 +630,57 @@
> EnvTypeKind),
> EnvTypeEntityName = type(EnvClassName, 0),
> EnvTypeFlags = env_type_decl_flags,
> - Fields = list__map(convert_local_to_field, LocalVars),
> + Fields0 = list__map(convert_local_to_field, LocalVars),
> +
> + ( Action = chain_gc_stack_frames ->
> + %
> + % insert the fixed fields:
> + % void *prev;
> + % void (*trace)(...);
> + %
> + PrevFieldName = data(var(var_name("prev", no))),
> + PrevFieldFlags = ml_gen_public_field_decl_flags,
> + PrevFieldType = mlds__generic_env_ptr_type,
> + PrevFieldDefnBody = mlds__data(PrevFieldType, no_initializer),
> + PrevFieldDecl = mlds__defn(PrevFieldName, Context,
> + PrevFieldFlags, PrevFieldDefnBody),
> +
> + TraceFieldName = data(var(var_name("trace", no))),
> + TraceFieldFlags = ml_gen_public_field_decl_flags,
> + TraceFieldType = mlds__generic_type, % XXX
> + TraceFieldDefnBody = mlds__data(TraceFieldType, no_initializer),
> + TraceFieldDecl = mlds__defn(TraceFieldName, Context,
> + TraceFieldFlags, TraceFieldDefnBody),
> +
> + Fields = [PrevFieldDecl, TraceFieldDecl | Fields0],
> +
Here you don't actually create a MR_StackChain structure but instead
add the two fields of this structure to the current structure. I would
update the comments about the generated code to reflect this.
> + %
> + % Set the initializer for the `prev' field
> + % to the global stack chain
> + % XXX we want to zero out the rest of the environment struct;
> + % will just not mentioning the remaining fields work?
> + %
> + StackChain = ml_stack_chain_var,
> + EnvInitializer = init_struct([init_obj(lval(StackChain))]),
> +
Does this work? I think if you want to explicitly zero it out, then you
need to do that, after all we can't assume anything about the backend.
I just checked the C backend and it doesn't do anything explicitly.
> + %
> + % Generate code to set the global stack chain
> + % to point to the current environment.
> + % stack_chain = env_ptr;
> + %
> + EnvPtrTypeName = ml_make_env_ptr_type(Globals, EnvTypeName),
> + EnvPtr = lval(var(qual(ModuleName,
> + mlds__var_name("env_ptr", no)),
> + EnvPtrTypeName)),
> + AssignToStackChain = assign(StackChain, EnvPtr),
> + LinkStackChain = [mlds__statement(atomic(AssignToStackChain),
> + Context)]
> + ;
> + Fields = Fields0,
> + EnvInitializer = no_initializer,
> + LinkStackChain = []
> + ),
> +
> Imports = [],
> Ctors = [], % mlds_to_il.m will add an empty constructor if needed
> Interfaces = [],
> @@ -623,25 +916,27 @@
>
> % Compute the name to use for the environment struct
> % for the specified function.
> -:- func ml_env_name(mlds__entity_name) = mlds__class_name.
> +:- func ml_env_name(mlds__entity_name, action) = mlds__class_name.
>
> -ml_env_name(type(_, _)) = _ :-
> +ml_env_name(type(_, _), _) = _ :-
> error("ml_env_name: expected function, got type").
> -ml_env_name(data(_)) = _ :-
> +ml_env_name(data(_), _) = _ :-
> error("ml_env_name: expected function, got data").
> -ml_env_name(function(PredLabel, ProcId, MaybeSeqNum, _PredId)) = ClassName :-
> +ml_env_name(function(PredLabel, ProcId, MaybeSeqNum, _PredId),
> + Action) = ClassName :-
> + Base = (if Action = chain_gc_stack_frames then "locals" else "env"),
Ok you have called it pointers, frame and now locals. Which one do you
want it to be?
> @@ -913,10 +1209,26 @@
>
> %
> % Succeed iff we should add the definition of this variable
> - % to the local_data field of the ml_elim_info, meaning that
> + % to the local_data field of the elim_info, meaning that
> % it should be added to the environment struct
> % (if it's a variable) or hoisted out to the top level
> % (if it's a static const).
This comment should also explain what you do when you are chaining stack
frames, ie add local_data which might contain a pointer.
> +:- pred ml_should_add_local_data(elim_info, mlds__var_name, mlds__type,
> + mlds__defns, mlds__statements).
> +:- mode ml_should_add_local_data(in, in, in, in, in) is semidet.
> +
> +ml_should_add_local_data(ElimInfo, VarName, Type,
> + FollowingDefns, FollowingStatements) :-
> + Action = ElimInfo ^ action,
> + (
> + Action = chain_gc_stack_frames,
> + ml_type_might_contain_pointers(Type) = yes
> + ;
> + Action = hoist_nested_funcs,
> + ml_need_to_hoist(ElimInfo ^ module_name, VarName,
> + FollowingDefns, FollowingStatements)
> + ).
> +
> %
> % This checks for a nested function definition
> % or static initializer that references the variable.
> @@ -955,6 +1267,59 @@
> initializer_contains_var(Initializer, QualVarName)
> ).
>
> + % Return `yes' if the type needs to be traced by
> + % the accurate garbage collector, i.e. if it might
> + % contain pointers.
> + %
> + % It's always safe to return `yes' here, so if in doubt, we do.
> + %
> + % For floats, we can return `no' even though they might
> + % get boxed in some circumstances, because if they are
> + % boxed then they will be represented as mlds__generic_type.
> + %
> + % Note that with --gcc-nested-functions,
> + % cont_type will be a function pointer that
> + % may point to a trampoline function,
> + % which might in fact contain pointers.
> + % But in that case we can't trace them,
> + % so that combination of options isn't supported.
> + % XXX we should add code to handle_options to check it.
> + % Hence we can return `no' here for mlds__cont_type.
> +
Add the code to handle_options.
> +:- func ml_type_might_contain_pointers(mlds__type) = bool.
> +
> +ml_type_might_contain_pointers(mercury_type(_Type, TypeCategory, _)) =
> + ml_type_category_might_contain_pointers(TypeCategory).
> +ml_type_might_contain_pointers(mercury_array_type(_)) = yes.
> +ml_type_might_contain_pointers(mlds__native_int_type) = no.
> +ml_type_might_contain_pointers(mlds__native_float_type) = no.
> +ml_type_might_contain_pointers(mlds__native_bool_type) = no.
> +ml_type_might_contain_pointers(mlds__native_char_type) = no.
> +ml_type_might_contain_pointers(mlds__foreign_type(_, _)) = yes.
> +ml_type_might_contain_pointers(mlds__class_type(_, _, Category)) =
> + (if Category = mlds__enum then no else yes).
> +ml_type_might_contain_pointers(mlds__ptr_type(_)) = yes.
> +ml_type_might_contain_pointers(mlds__array_type(_)) = yes.
> +ml_type_might_contain_pointers(mlds__func_type(_)) = no.
> +ml_type_might_contain_pointers(mlds__generic_type) = yes.
> +ml_type_might_contain_pointers(mlds__generic_env_ptr_type) = yes.
> +ml_type_might_contain_pointers(mlds__pseudo_type_info_type) = yes.
> +ml_type_might_contain_pointers(mlds__cont_type(_)) = no.
> +ml_type_might_contain_pointers(mlds__commit_type) = no.
> +ml_type_might_contain_pointers(mlds__rtti_type(_)) = yes.
> +ml_type_might_contain_pointers(mlds__unknown_type) = yes.
> +
> +:- func ml_type_category_might_contain_pointers(builtin_type) = bool.
> +ml_type_category_might_contain_pointers(int_type) = no.
> +ml_type_category_might_contain_pointers(char_type) = no.
> +ml_type_category_might_contain_pointers(str_type) = yes.
> +ml_type_category_might_contain_pointers(float_type) = no.
> +ml_type_category_might_contain_pointers(pred_type) = yes.
> +ml_type_category_might_contain_pointers(tuple_type) = yes.
> +ml_type_category_might_contain_pointers(enum_type) = no.
> +ml_type_category_might_contain_pointers(polymorphic_type) = yes.
> +ml_type_category_might_contain_pointers(user_type) = yes.
> +
> Index: runtime/mercury.c
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/runtime/mercury.c,v
> retrieving revision 1.25
> diff -u -d -r1.25 mercury.c
> --- runtime/mercury.c 31 May 2001 06:00:10 -0000 1.25
> +++ runtime/mercury.c 8 Nov 2001 16:16:54 -0000
> @@ -26,6 +26,8 @@
> ** Variable definitions
> */
>
> +void *mercury__private_builtin____stack_chain;
> +
> MR_Word mercury__private_builtin__dummy_var;
>
> /*---------------------------------------------------------------------------*/
> Index: runtime/mercury.h
> ===================================================================
> RCS file: /home/mercury1/repository/mercury/runtime/mercury.h,v
> retrieving revision 1.41
> diff -u -d -r1.41 mercury.h
> --- runtime/mercury.h 22 Nov 2001 16:22:29 -0000 1.41
> +++ runtime/mercury.h 26 Nov 2001 13:06:12 -0000
> @@ -273,10 +273,23 @@
> typedef struct MR_FO_PseudoTypeInfo_Struct19 MR_FO_PseudoTypeInfo_Struct19;
> typedef struct MR_FO_PseudoTypeInfo_Struct20 MR_FO_PseudoTypeInfo_Struct20;
>
> +/* The chain of stack frames, used for accurate GC. */
> +struct MR_StackChain {
> + struct MR_StackChain *prev;
> + void (*trace)(void *this_frame);
> +};
> +
> /*---------------------------------------------------------------------------*/
> /*
> ** Declarations of contants and variables
> */
> +
> +/*
> +** This points to the start of the MR_StackChain frame list.
> +** XXX Using a global variable for this is not thread-safe.
> +** We should probably use a GNU C global register variable.
> +*/
> +extern void *mercury__private_builtin__stack_chain;
>
I suggest changing this code so that if someone attempts to do this the
problem will be quickly found, and then they can correct it.
#ifdef ACCURATE_GC
#ifdef MR_THREAD_SAFE
#error "NYI"
#else
extern void *mercury__private_builtin__stack_chain;
#endif
#endif
--------------------------------------------------------------------------
mercury-reviews mailing list
post: mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the reviews
mailing list