[m-rev.] For review: adding a new package for RBMM

Julien Fischer juliensf at csse.unimelb.edu.au
Tue May 22 16:08:26 AEST 2007


On Tue, 22 May 2007, Quan Phan wrote:

> Estimated hours taken: 85
> Branches: main
>
> Add a package for region-based memory management. Currently it contains
> a region analysis which divides the heap used by a program into regions and
> reasons about their lifetime,

I suggest:

 	... and determines the lifetimes of those regions.

instead of:

 	... and reasons about their lifetimes.


> based on those data it then detects the points
> where instructions for creating and destroying regions can be added.

I suggest:

 	Based on this analysis it then determines where instructions for
 	creating and destroying regions can be added.

> Currently it supports single-module program and only gathers the necessary
> information, runtime support for regions and support for multi-module
> programs
> will be implemented in subsequent development.
>
> Quan.
>
> compiler/rbmm.m:
> 	Top-level of the region-based memory management (RBMM) sub-package.
> 	This is part of the transform_hlds package.
>
> compiler/rbmm.execution_path.m
> 	Collecting execution paths in procedures.

s/Collecting/Collections/

>
> compiler/rbmm.interproc_region_lifetime.m
> 	Reasoning about region lifetime acroos procedure boundary.


s/acroos/across/

> compiler/rbmm.live_region_analysis.m
> 	Deriving live regions before and after program points in
> 	execution paths.
>
> compiler/rbmm.live_variable_analysis.m
> 	Computing live variables before and after program points in

s/Computing/Compute/

> 	execution paths.
>
> compiler/rbmm.points_to_analysis.m
> 	Implementing the region points-to analysis.

s/Implementing/Implement/

>
> compiler/rbmm.points_to_graph.m
> 	Region points-to graph which represents the heap in terms of regions.
>
> compiler/rbmm.points_to_info.m
> 	Defining the information that is collected during region points-to
> 	analysis.
>
> compiler/rbmm.region_instruction.m
> 	Introducing region instructions along execution paths.
>
> compiler/rbmm.region_liveness_info.m
> 	Defining the analysis information for live variable and live region
> 	analysis.
>
> compiler/smm_common.m:
>        Utilities that may be used in static memory management: CTGC and
> RBMM.
>
> 	XXX There is currently some code duplication between the
> 	CTGC and RBMM systems.  This will be removed in a susbsequent
> 	change.
>
> All the above are new files.
>
> compiler/transform_hlds.m:
> 	Add sub-modules rbmm and smm_common.
>
> compiler/fixpoint_table.m:
> 	A general fixpoint computation.

As we discussed in person, put this in smm_fixpoint_table.m.
(We will need to merge this with the CTGC version after this.)

>
> 	XXX A file with similar functionality is duplicated in CTGC code.
> 	fixpoint_table.m is only used in rbmm.points_to_info.m.
>
> compiler/mercury_compile.m
> 	Import package rbmm and call to the region analysis.
>
> compiler/options.m
> 	Add option --region-analysis.

This option also needs to be documented in the user's guide.
(In doc/user_guide.texi.)

...

> Index: options.m
> ===================================================================
> RCS file: /home/mercury/mercury1/repository/mercury/compiler/options.m,v
> retrieving revision 1.558
> diff -u -u -r1.558 options.m
> --- options.m	14 May 2007 08:20:10 -0000	1.558
> +++ options.m	16 May 2007 00:11:26 -0000
> @@ -293,6 +293,7 @@
>     ;       record_term_sizes_as_words
>     ;       record_term_sizes_as_cells
>     ;       experimental_complexity
> +    ;       region_analysis
>
>     % (c) Miscellaneous
>     ;       gc
> @@ -1070,6 +1071,7 @@
>     record_term_sizes_as_words          -   bool(no),
>     record_term_sizes_as_cells          -   bool(no),
>     experimental_complexity             -   string(""),
> +    region_analysis                     -   bool(no),
>     % (c) Miscellaneous optional features
>     gc                                  -   string("boehm"),
>     parallel                            -   bool(no),
> @@ -1836,6 +1838,7 @@
> long_option("record-term-sizes-as-words", record_term_sizes_as_words).
> long_option("record-term-sizes-as-cells", record_term_sizes_as_cells).
> long_option("experimental-complexity",  experimental_complexity).
> +long_option("region-analysis",      region_analysis).
> % (c) miscellaneous optional features
> long_option("gc",                   gc).
> long_option("garbage-collection",   gc).
> @@ -3655,7 +3658,9 @@
>         "\tEnable experimental complexity analysis for the predicates",
>         "\tlisted in the given file.",
>         "\tThis option is supported for the C back-end, with",
> -        "\t--no-highlevel-code."
> +        "\t--no-highlevel-code.",
> +        "--region-analysis",
> +        "\tEnable the analysis for region based memory management"
>     ]),

Comment this documentation out for now.

...

> Index: rbmm.interproc_region_lifetime.m
> ===================================================================
> RCS file: rbmm.interproc_region_lifetime.m
> diff -N rbmm.interproc_region_lifetime.m
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ rbmm.interproc_region_lifetime.m	22 May 2007 01:56:11 -0000
> @@ -0,0 +1,644 @@
> +%-----------------------------------------------------------------------------%
> +% vim: ft=mercury ts=4 sw=4 et
> +%-----------------------------------------------------------------------------%
> +% Copyright (C) 2005-2007 The University of Melbourne.
> +% This file may only be copied under the terms of the GNU General
> +% Public License - see the file COPYING in the Mercury distribution.
> +%-----------------------------------------------------------------------------%
> +%
> +% File rbmm.interproc_region_lifetime.m.
> +% Main author: Quan Phan.
> +%
> +% This module detects lifetime of regions across procedure boundary. It
> +% updates the initial bornR and deadR, then computing constantR for each
> +% procedure. It also provides a predicate to eliminate primitive regions
> +% (i.e., ones that do not actually exist if primitive values are not boxed)
> +% from analysis information.
> +%
> +%-----------------------------------------------------------------------------%
> +
> +:- module transform_hlds.rbmm.interproc_region_lifetime.
> +:- interface.
> +
> +:- import_module hlds.
> +:- import_module hlds.hlds_module.
> +:- import_module transform_hlds.rbmm.points_to_info.
> +:- import_module transform_hlds.rbmm.region_liveness_info.
> +
> +%-----------------------------------------------------------------------------%
> +
> +    % This predicate reasons about lifetime of regions across procedure
> +    % boundary. It will update the initial deadR and bornR sets and compute
> +    % constantR set.
> +    %
> +:- pred compute_interproc_region_lifetime(module_info::in,
> +    rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::in,
> +    proc_region_set_table::out, proc_region_set_table::in,
> +    proc_region_set_table::out, proc_region_set_table::in,
> +    proc_region_set_table::out) is det.
> +
> +    % This predicate removes regions of primitive types from the input
> data
> +    % structures.
> +    % The reason for this is the assumption that primitive values are not
> +    % boxed (i.e., not store in regions).
> +    %
> +:- pred ignore_primitive_regions(module_info::in, rpta_info_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out,
> +    proc_region_set_table::in, proc_region_set_table::out,
> +    proc_region_set_table::in, proc_region_set_table::out,
> +    proc_region_set_table::in, proc_region_set_table::out,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::out,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::out,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::out) is det.
> +

...

> +compute_interproc_region_lifetime(ModuleInfo, RptaInfoTable, ExecPathTable,
> +        LRBeforeTable, LRAfterTable, InputRTable, OutputRTable,
> +        ConstantRTable, !BornRTable, !DeadRTable) :-
> +
> +    apply_live_region_born_removal_rules(ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable, !BornRTable),
> +	apply_live_region_dead_removal_rules(ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable, !DeadRTable),
> +	map.foldl(compute_constantR(InputRTable, OutputRTable, !.BornRTable),
> +		!.DeadRTable, map.init, ConstantRTable).
> +
> +:- pred compute_constantR(proc_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::in,
> +	pred_proc_id::in, region_set::in, proc_region_set_table::in,
> +    proc_region_set_table::out) is det.
> +
> +compute_constantR(InputRTable, OutputRTable, BornRTable, PPId, DeadR,
> +        !ConstantRTable) :-
> +	map.lookup(InputRTable, PPId, InputR),
> +	map.lookup(OutputRTable, PPId, OutputR),
> +	map.lookup(BornRTable, PPId, BornR),
> +	set.union(InputR, OutputR, InputOutputR0),
> +	set.difference(InputOutputR0, BornR, InputOutputR),
> +	set.difference(InputOutputR, DeadR, ConstantR),
> +	svmap.set(PPId, ConstantR, !ConstantRTable).
> +
> +	% Apply the live region analysis rules to update bornR and deadR
> +	% sets of each procedure.
> +    %
> +:- pred apply_live_region_dead_removal_rules(module_info::in,
> +	rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out) is det.
> +
> +apply_live_region_dead_removal_rules(ModuleInfo, RptaInfoTable,
> ExecPathTable,
> +        LRBeforeTable, LRAfterTable, !DeadRTable) :-
> +	apply_live_region_rule(dead_removal_rules, ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable, !DeadRTable).
> +
> +:- pred apply_live_region_born_removal_rules(module_info::in,
> +    rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out) is det.
> +
> +apply_live_region_born_removal_rules(ModuleInfo, RptaInfoTable,
> ExecPathTable,
> +	LRBeforeTable, LRAfterTable, !BornRTable) :-
> +	apply_live_region_rule(born_removal_rules, ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable, !BornRTable).
> +
> +:- type rule_pred == (
> +        pred(pred_proc_id, region_set, region_set, proc_region_set_table,
> +            map(rptg_node, rptg_node), region_set)
> +    ).
> +:- inst rule_pred == ( pred(in, in, in, in, in, out) is det ).
> +
> +:- pred apply_live_region_rule(rule_pred::in(rule_pred), module_info::in,
> +    rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out) is det.
> +
> +apply_live_region_rule(Rule, ModuleInfo, RptaInfoTable, ExecPathTable,
> +    LRBeforeTable, LRAfterTable, !ProcRegionSetTable) :-
> +	module_info_ensure_dependency_info(ModuleInfo, ModuleInfo1),
> +	module_info_get_maybe_dependency_info(ModuleInfo1, MaybeDepInfo),
> +	(
> +		MaybeDepInfo = yes(DepInfo)
> +	->
> +		hlds.hlds_module.hlds_dependency_info_get_dependency_ordering(
> +			DepInfo, DepOrdering),
> +		run_with_dependencies(Rule, DepOrdering, ModuleInfo1,
> +            RptaInfoTable, ExecPathTable, LRBeforeTable, LRAfterTable,
> +            !ProcRegionSetTable)
> +	;
> +		unexpected(this_file,
> +            "apply_live_region_rule: no dependency info")
> +	).
> +
> +:- pred run_with_dependencies(rule_pred::in(rule_pred),
> +    dependency_ordering::in, module_info::in, rpta_info_table::in,
> +    execution_path_table::in, proc_pp_region_set_table::in,
> +    proc_pp_region_set_table::in, proc_region_set_table::in,
> +    proc_region_set_table::out) is det.
> +
> +run_with_dependencies(Rule, Deps, ModuleInfo, RptaInfoTable, ExecPathTable,
> +        LRBeforeTable, LRAfterTable, !ProcRegionSetTable) :-
> +    % We want to proceed the SCC graph top-down so reverse the list
> +    % (the process is foldr2, but it is not yet in list module)
> +	list.reverse(Deps, Deps1),
> +	list.foldl(run_with_dependency(Rule, ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable), Deps1,
> +        !ProcRegionSetTable).
> +
> +:- pred run_with_dependency(rule_pred::in(rule_pred),
> +    module_info::in, rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    list(pred_proc_id)::in, proc_region_set_table::in,
> +    proc_region_set_table::out) is det.
> +
> +run_with_dependency(Rule, ModuleInfo, RptaInfoTable, ExecPathTable,
> +        LRBeforeTable, LRAfterTable, SCC, !ProcRegionSetTable) :-
> +	(
> +        % Ignores special predicates.
> +		some_are_special_preds(SCC, ModuleInfo)
> +	->
> +		true
> +	;
> +        % Perform a fixpoint computation for each strongly connected
> +        % component.
> +		run_with_dependency_until_fixpoint(Rule, SCC, ModuleInfo,
> +            RptaInfoTable, ExecPathTable, LRBeforeTable, LRAfterTable,
> +            !ProcRegionSetTable)
> +	).
> +
> +:- pred run_with_dependency_until_fixpoint(rule_pred::in(rule_pred),
> +    list(pred_proc_id)::in, module_info::in, rpta_info_table::in,
> +    execution_path_table::in, proc_pp_region_set_table::in,
> +    proc_pp_region_set_table::in, proc_region_set_table::in,
> +    proc_region_set_table::out) is det.
> +
> +run_with_dependency_until_fixpoint(Rule, SCC, ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable, !ProcRegionSetTable) :-
> +    % This call calculates the region set for each procedure in SCC
> +	list.foldl(apply_rule_pred_proc(Rule, ModuleInfo, RptaInfoTable,
> +        ExecPathTable, LRBeforeTable, LRAfterTable),
> +        SCC, !.ProcRegionSetTable, ProcRegionSetTable1),
> +	(
> +		proc_region_set_table_equal(ProcRegionSetTable1,
> +            !.ProcRegionSetTable)
> +	->
> +		% If all region_set's in the FPTable are intact update the main
> +        % ProcRegionSetTable.
> +		!:ProcRegionSetTable = ProcRegionSetTable1
> +	;
> +		% Some is not fixed, start all over again
> +		run_with_dependency_until_fixpoint(Rule, SCC, ModuleInfo,
> +            RptaInfoTable, ExecPathTable, LRBeforeTable, LRAfterTable,
> +            ProcRegionSetTable1, !:ProcRegionSetTable)
> +	).
> +
> +:- pred apply_rule_pred_proc(rule_pred::in(rule_pred),
> +    module_info::in, rpta_info_table::in, execution_path_table::in,
> +    proc_pp_region_set_table::in, proc_pp_region_set_table::in,
> +    pred_proc_id::in, proc_region_set_table::in,
> proc_region_set_table::out)
> +    is det.
> +
> +apply_rule_pred_proc(Rule, ModuleInfo, RptaInfoTable, ExecPathTable,
> +        LRBeforeTable, LRAfterTable, PPId, !ProcRegionSetTable) :-
> +    % We need to follow each execution path and apply the two rules when
> +    % possible
> +	map.lookup(RptaInfoTable, PPId, RptaInfo),
> +	map.lookup(ExecPathTable, PPId, EPs),
> +	map.lookup(LRBeforeTable, PPId, ProcLRBefore),
> +	map.lookup(LRAfterTable, PPId, ProcLRAfter),
> +
> +    % Here we analysing a caller but will update the region sets of its
> +    % callees.
> +	apply_live_region_rules_eps(Rule, EPs, ExecPathTable, ModuleInfo,
> +        PPId, RptaInfo, RptaInfoTable, ProcLRBefore, ProcLRAfter,
> +        !ProcRegionSetTable).
> +
> +:- pred apply_live_region_rules_eps(rule_pred::in(rule_pred),
> +    list(execution_path)::in, execution_path_table::in, module_info::in,
> +    pred_proc_id::in, rpta_info::in, rpta_info_table::in,
> +    pp_region_set_table::in, pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out) is det.
> +
> +apply_live_region_rules_eps(_Rule, [], _, _, _, _, _, _, _,
> +        !ProcRegionSetTable).
> +apply_live_region_rules_eps(Rule, [ExecPath|ExecPaths], ExecPathTable,
> +        ModuleInfo, PPId, RptaInfo, RptaInfoTable, ProcLRBefore,
> +        ProcLRAfter, !ProcRegionSetTable) :-
> +	apply_live_region_rules_ep(Rule, ExecPath, ExecPathTable, ModuleInfo,
> +        PPId, RptaInfo, RptaInfoTable, ProcLRBefore, ProcLRAfter,
> +        !ProcRegionSetTable),
> +	apply_live_region_rules_eps(Rule, ExecPaths, ExecPathTable, ModuleInfo,
> +        PPId, RptaInfo, RptaInfoTable, ProcLRBefore, ProcLRAfter,
> +        !ProcRegionSetTable).
> +
> +	% Follow each execution path of a procedure and update deadR and bornR
> +    % sets
> +    %
> +:- pred apply_live_region_rules_ep(rule_pred::in(rule_pred),
> +    execution_path::in, execution_path_table::in, module_info::in,
> +    pred_proc_id::in, rpta_info::in, rpta_info_table::in,
> +    pp_region_set_table::in, pp_region_set_table::in,
> +    proc_region_set_table::in, proc_region_set_table::out) is det.
> +
> +apply_live_region_rules_ep(_Rule, [], _, _, _, _, _, _, _,
> +        !ProcRegionSetTable).
> +apply_live_region_rules_ep(Rule, [ProgPoint - Goal | ProgPoint_Goals],
> +        ExecPathTable, ModuleInfo, PPId, RptaInfo, RptaInfoTable,
> +        ProcLRBefore, ProcLRAfter, !ProcRegionSetTable) :-
> +	Goal = hlds_goal(Expr, _Info),
> +    % The updating will only happen at call sites, i.e., when the goal
> +    % at the program point is a procedure call.
> +	( if
> +		Expr = plain_call(PredId_q, ProcId_q, _, _, _, _)
> +	  then
> +	  	PPId_q = proc(PredId_q, ProcId_q),

I suggest:

 	s/PredId_q/CalleePredId/
 	s/ProcId_q/CalleeProcId/
 	s/PPId_q/CalleePPId/
...

> Index: rbmm.live_region_analysis.m
> ===================================================================
> RCS file: rbmm.live_region_analysis.m
> diff -N rbmm.live_region_analysis.m
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ rbmm.live_region_analysis.m	21 May 2007 13:12:31 -0000
> @@ -0,0 +1,260 @@
> +%-----------------------------------------------------------------------------%
> +% vim: ft=mercury ts=4 sw=4 et
> +%-----------------------------------------------------------------------------%
> +% Copyright (C) 2005-2007 The University of Melbourne.
> +% This file may only be copied under the terms of the GNU General
> +% Public License - see the file COPYING in the Mercury distribution.
> +%-----------------------------------------------------------------------------%
> +%
> +% File rbmm.live_region_analysis.m.
> +% Main author: Quan Phan.
> +%
> +% This module implements the live region analysis, which uses ExecPaths

s/ExecPaths/execution paths/

> with
> +% live variables to collect live regions at each program point.
> +%
> +%-----------------------------------------------------------------------------%
> +
> +:- module transform_hlds.rbmm.live_region_analysis.
> +:- interface.
> +
> +:- import_module hlds.
> +:- import_module hlds.hlds_module.
> +:- import_module transform_hlds.rbmm.points_to_info.
> +:- import_module transform_hlds.rbmm.region_liveness_info.
> +
> +%-----------------------------------------------------------------------------%
> +
> +	% Collects live region sets.
> +    %
> +:- pred live_region_analysis(module_info::in, rpta_info_table::in,
> +    proc_pp_varset_table::in, proc_pp_varset_table::in,
> +    proc_pp_varset_table::in, proc_pp_region_set_table::out,
> +    proc_pp_region_set_table::out, proc_pp_region_set_table::out,
> +    proc_region_set_table::out, proc_region_set_table::out,
> +    proc_region_set_table::out, proc_region_set_table::out,
> +    proc_region_set_table::out) is det.
> +

...

> +%----------------------------------------------------------------------------%
> +%
> +% Live region analysis
> +%
> +
> +% Compute for each procedure live region sets before and after all program
> +% points. As in live variable analysis we calculated the set of void
> +% variables after a program point, this analysis also computes the set of
> +% regions of those variables.
> +%
> +% Apart from that, it is convenient to also compute the inputR, outputR,
> +% localR, and the initial bornR and deadR for each procedure in this
> +% analysis.
> +%
> +

...

> +:- pred proc_lv_to_proc_lr(rpt_graph::in, module_info::in, proc_info::in,
> +	program_point::in, variable_set::in, pp_region_set_table::in,
> +    pp_region_set_table::out) is det.
> +
> +proc_lv_to_proc_lr(Graph, ModuleInfo, ProcInfo, ProgPoint, LVs,
> !ProcLRMap) :-
> +	lv_to_lr(LVs, Graph, ModuleInfo, ProcInfo, LRs),
> +	svmap.set(ProgPoint, LRs, !ProcLRMap).
> +
> +:- pred foldl8(pred(L, A, A, B, B, C, C, D, D, E, E, F, F, G, G, H, H),
> +    list(L),
> +	A, A, B, B, C, C, D, D, E, E, F, F, G, G, H, H).
> +:- mode foldl8(pred(in, in, out, in, out, in, out, in, out, in, out,
> in, out,
> +    in, out, in, out) is det,
> +	in, in, out, in, out, in, out, in, out, in, out, in, out, in, out, in,
> +    out) is det.
> +
> +foldl8(_, [], !A, !B, !C, !D, !E, !F, !G, !H).
> +foldl8(P, [H|T], !A, !B, !C, !D, !E, !F, !G, !H) :-
> +	call(P, H, !A, !B, !C, !D, !E, !F, !G, !H),
> +	foldl8(P, T, !A, !B, !C, !D, !E, !F, !G, !H).


That should be added to the standard library.  (Do so as a separate
change though.)

> +    % From a set of live variables, derive the set of live regions.
> +    % A live region is defined to be reachable from a live variable
> +    % in the corresponding region points-to graph.
> +    %
> +:- pred lv_to_lr(variable_set::in, rpt_graph::in, module_info::in,
> +    proc_info::in, region_set::out) is det.
> +
> +lv_to_lr(LVSet, Graph, ModuleInfo, ProcInfo, LRSet) :-
> +	(
> +		set.empty(LVSet)
> +	->
> +		set.init(LRSet)
> +	;
> +        % collect reachable regions at this pp

Rewrite that comment as:

 	Collect reachable regions at this program point.

> +		set.init(LRSet0),
> +		set.fold(reach_from_a_variable(Graph, ModuleInfo, ProcInfo),
> +            LVSet, LRSet0, LRSet)
> +	).
> +

Assuming this change bootchecks I'm happy for you to commit it.
The following is a rough plan of how we need to proceed with
the rbmm system from here (and who needs to be involved).

 	(1) commit this (qph)
 	(2) merge rbmm and ctgc common code (juliensf)
 	(3) runtime support for rbmm (qph and zs)
 	(4) annotate HLDS with rbmm info (qph and juliensf)
 	(5) support intermodule region analysis
 		(qph, juliensf and wangp)
 	(6) handle higher-order and foreign_procs (qph, ?)

Most of the stages from (2) onwards can be carried out concurrently,
which is one reason for getting the initial version committed now.

Julien.
--------------------------------------------------------------------------
mercury-reviews mailing list
Post messages to:       mercury-reviews at csse.unimelb.edu.au
Administrative Queries: owner-mercury-reviews at csse.unimelb.edu.au
Subscriptions:          mercury-reviews-request at csse.unimelb.edu.au
--------------------------------------------------------------------------



More information about the reviews mailing list