fix optimizer bug related to single-use functions
Fix problem with once-use tracking and delayed variable-use marking that is performed for local function bodies. A delayed variable-use registration might happen after a once-used variable is replaced by its use. This scenario is difficult to provoke, because the optimizer has to first decide not to move a once-use function, and in a latter pass decide to move it after all. There's not enough information to retract the tentative use plus its transitive implications. The solution is to avoid the generic once-use layer for `lambda` forms whose uses are delayed (and that likely has a good effect on inlining anyway). The other half of the solution is to avoid transitive use marking on a once-used variable whose expression has been moved (and there are no transitive things to skip, because that expression isn't a `lambda` form).
This commit is contained in:
parent
a7ac75f15d
commit
f19d655cbc
|
@ -5899,6 +5899,61 @@
|
|||
'(lambda (x)
|
||||
(list (eq? x 7) (box 5))))
|
||||
|
||||
|
||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Regression test to check that the optimizer doesn't
|
||||
;; get confused in handling a single-use function that
|
||||
;; is too large to be inlined into multiple uses.
|
||||
|
||||
;; The optimizer had a bad interaction between delayed
|
||||
;; use marking of functions and moving single-use
|
||||
;; expressions, which somehow is relevant in this
|
||||
;; module. The fact that the code is at compile time
|
||||
;; may have been relevant for limiting cross-module inlining.
|
||||
|
||||
(module optimizer-single-use-function-test racket/base
|
||||
(require (for-syntax racket/base
|
||||
syntax/parse
|
||||
racket/list
|
||||
syntax/stx
|
||||
racket/syntax))
|
||||
|
||||
(define-syntax (define-mongo-struct-field stx)
|
||||
(syntax-parse stx
|
||||
[#:ref
|
||||
(list 'x
|
||||
'mongo-dict-ref)]
|
||||
[#:set!
|
||||
(list 'x
|
||||
'mongo-dict-set!)]
|
||||
[#:inc
|
||||
(list (format-id 'struct "inc-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-inc!)]
|
||||
[#:null
|
||||
(list (format-id 'struct "null-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-remove!)]
|
||||
[#:push
|
||||
(list (format-id 'struct "push-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-push!)]
|
||||
[#:append
|
||||
(list (format-id 'struct "append-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-append!)]
|
||||
[#:set-add
|
||||
(format-id 'struct "set-add-~a-~a!" 'struct 'field)]
|
||||
[#:set-add*
|
||||
(format-id 'struct "set-add*-~a-~a!" 'struct 'field)]
|
||||
[#:pop
|
||||
(list (format-id 'struct "pop-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-pop!)]
|
||||
[#:shift
|
||||
(list (format-id 'struct "shift-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-shift!)]
|
||||
[#:pull
|
||||
(list (format-id 'struct "pull-~a-~a!" 'struct 'field)
|
||||
'mongo-dict-pull!)]
|
||||
[#:pull* 'pull]
|
||||
[_ 'err])))
|
||||
|
||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(report-errs)
|
||||
|
|
|
@ -159,6 +159,8 @@ static void optimize_info_seq_init(Optimize_Info *info, Optimize_Info_Sequence *
|
|||
static void optimize_info_seq_step(Optimize_Info *info, Optimize_Info_Sequence *info_seq);
|
||||
static void optimize_info_seq_done(Optimize_Info *info, Optimize_Info_Sequence *info_seq);
|
||||
|
||||
static int ir_propagate_ok(Scheme_Object *o, Optimize_Info *info, int used_once, Scheme_IR_Local *once_var);
|
||||
|
||||
static Scheme_Object *estimate_closure_size(Scheme_Object *e);
|
||||
static Scheme_Object *no_potential_size(Scheme_Object *value);
|
||||
|
||||
|
@ -2095,7 +2097,8 @@ static int movable_expression(Scheme_Object *expr, Optimize_Info *info,
|
|||
case scheme_ir_lambda_type:
|
||||
case scheme_case_lambda_sequence_type:
|
||||
/* Can't move across lambda or continuation if not closed, since
|
||||
that changes allocation of a closure. */
|
||||
that changes allocation of a closure (i.e., might allocate the
|
||||
closure multiple times). */
|
||||
return !cross_lambda && !cross_k;
|
||||
default:
|
||||
if (SCHEME_TYPE(expr) > _scheme_ir_values_types_)
|
||||
|
@ -4451,7 +4454,7 @@ static Scheme_Object *finish_optimize_application2(Scheme_App2_Rec *app, Optimiz
|
|||
} else if (SAME_OBJ(var, scheme_false)) {
|
||||
return replace_tail_inside(scheme_false, inside, app->rand);
|
||||
} else {
|
||||
if (var && scheme_ir_propagate_ok(var, info)) {
|
||||
if (var && ir_propagate_ok(var, info, 1, NULL)) {
|
||||
/* can propagate => is a constant */
|
||||
return replace_tail_inside(scheme_true, inside, app->rand);
|
||||
}
|
||||
|
@ -6678,15 +6681,27 @@ int scheme_is_liftable(Scheme_Object *o, Scheme_Hash_Tree *exclude_vars, int fue
|
|||
return 0;
|
||||
}
|
||||
|
||||
int scheme_ir_propagate_ok(Scheme_Object *value, Optimize_Info *info)
|
||||
/* Can we constant-propagate the expression `value`? */
|
||||
int ir_propagate_ok(Scheme_Object *value, Optimize_Info *info, int used_once, Scheme_IR_Local *once_var)
|
||||
/* Can we constant-propagate the expression `value`?
|
||||
If `used_once` is true, the value is known to be used once,
|
||||
but if `once_var` is provided, record when the result
|
||||
relies on that once-usedness. */
|
||||
{
|
||||
if (SAME_TYPE(SCHEME_TYPE(value), scheme_ir_lambda_type)) {
|
||||
int sz;
|
||||
sz = lambda_body_size_plus_info((Scheme_Lambda *)value, 1, info, NULL);
|
||||
if ((sz >= 0) && (sz <= MAX_PROC_INLINE_SIZE))
|
||||
return 1;
|
||||
else {
|
||||
else if (used_once) {
|
||||
if (once_var) {
|
||||
/* Mark the variable as having a known value only as long as it's used just
|
||||
once. In case the one reference is duplicated --- perhaps because it is
|
||||
used in a non-application position in a function that is itself inlined
|
||||
--- then the known value should be cleared. */
|
||||
once_var->optimize.clear_known_on_multi_use = 1;
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
Scheme_Lambda *lam = (Scheme_Lambda *)value;
|
||||
if (sz < 0)
|
||||
scheme_log(info->logger,
|
||||
|
@ -6716,7 +6731,7 @@ int scheme_ir_propagate_ok(Scheme_Object *value, Optimize_Info *info)
|
|||
Scheme_Case_Lambda *cl = (Scheme_Case_Lambda *)value;
|
||||
int i;
|
||||
for (i = cl->count; i--; ) {
|
||||
if (!scheme_ir_propagate_ok(cl->array[i], info))
|
||||
if (!ir_propagate_ok(cl->array[i], info, used_once, once_var))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
@ -7674,7 +7689,10 @@ static Scheme_Object *optimize_lets(Scheme_Object *form, Optimize_Info *info, in
|
|||
if (value)
|
||||
value = extract_specialized_proc(value, value);
|
||||
|
||||
if (value && (scheme_ir_propagate_ok(value, body_info))) {
|
||||
if (value && ir_propagate_ok(value,
|
||||
body_info,
|
||||
(!indirect && (pre_body->vars[0]->use_count == 1)),
|
||||
pre_body->vars[0])) {
|
||||
pre_body->vars[0]->optimize.known_val = value;
|
||||
did_set_value = 1;
|
||||
} else if (value && !is_rec) {
|
||||
|
@ -7709,6 +7727,7 @@ static Scheme_Object *optimize_lets(Scheme_Object *form, Optimize_Info *info, in
|
|||
once_vclock, once_aclock, once_kclock, once_sclock,
|
||||
once_increments_kclock);
|
||||
pre_body->vars[0]->optimize.known_val = (Scheme_Object *)once_used;
|
||||
pre_body->vars[0]->optimize.clear_known_on_multi_use = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7805,7 +7824,7 @@ static Scheme_Object *optimize_lets(Scheme_Object *form, Optimize_Info *info, in
|
|||
irlv->value = value;
|
||||
|
||||
if (!irlv->vars[0]->mutated) {
|
||||
if (scheme_ir_propagate_ok(value, rhs_info)) {
|
||||
if (ir_propagate_ok(value, rhs_info, irlv->vars[0]->use_count == 1, irlv->vars[0])) {
|
||||
/* Register re-optimized as the value for the binding, but
|
||||
maybe only if it didn't grow too much: */
|
||||
int new_sz;
|
||||
|
@ -8815,7 +8834,7 @@ module_optimize(Scheme_Object *data, Optimize_Info *info, int context)
|
|||
info);
|
||||
|
||||
if (n == 1) {
|
||||
if (scheme_ir_propagate_ok(e, info))
|
||||
if (ir_propagate_ok(e, info, 0, NULL))
|
||||
cnst = 1;
|
||||
else if (scheme_is_statically_proc(e, info, OMITTABLE_IGNORE_APPN_OMIT)) {
|
||||
cnst = 1;
|
||||
|
@ -8997,7 +9016,7 @@ module_optimize(Scheme_Object *data, Optimize_Info *info, int context)
|
|||
scheme_hash_set(originals, scheme_make_integer(start_simultaneous), old_e);
|
||||
}
|
||||
|
||||
if (!scheme_ir_propagate_ok(e, info)
|
||||
if (!ir_propagate_ok(e, info, 0, NULL)
|
||||
&& scheme_is_statically_proc(e, info, 0)) {
|
||||
/* If we previously installed a procedure for inlining,
|
||||
don't replace that with a worse approximation. */
|
||||
|
@ -9396,7 +9415,7 @@ static void increment_use_count(Scheme_IR_Local *var, int as_rator)
|
|||
var->non_app_count++;
|
||||
|
||||
if (var->optimize.known_val
|
||||
&& SAME_TYPE(SCHEME_TYPE(var->optimize.known_val), scheme_once_used_type))
|
||||
&& var->optimize.clear_known_on_multi_use)
|
||||
var->optimize.known_val = NULL;
|
||||
}
|
||||
|
||||
|
@ -9758,6 +9777,12 @@ static Scheme_Once_Used *make_once_used(Scheme_Object *val, Scheme_IR_Local *var
|
|||
{
|
||||
Scheme_Once_Used *o;
|
||||
|
||||
/* Procedures should be handled more specifically, because there are
|
||||
issues with transitive delayed-use registration to handle
|
||||
`letrec`, where a value that has already been moved can be
|
||||
marked later as used. */
|
||||
MZ_ASSERT(!SCHEME_LAMBDAP(val));
|
||||
|
||||
o = MALLOC_ONE_TAGGED(Scheme_Once_Used);
|
||||
o->so.type = scheme_once_used_type;
|
||||
|
||||
|
@ -9865,7 +9890,15 @@ static void register_transitive_uses(Scheme_IR_Local *var, Optimize_Info *info)
|
|||
for (j = 0; j < ht->size; j++) {
|
||||
if (ht->vals[j]) {
|
||||
tvar = SCHEME_VAR(ht->keys[j]);
|
||||
register_use(tvar, info);
|
||||
|
||||
if (tvar->optimize.known_val
|
||||
&& SAME_TYPE(SCHEME_TYPE(tvar->optimize.known_val), scheme_once_used_type)
|
||||
&& ((Scheme_Once_Used *)tvar->optimize.known_val)->moved) {
|
||||
/* variable no longer used, and any transitive uses were
|
||||
covered by re-optimizing in its use context */
|
||||
MZ_ASSERT(!tvar->optimize_used);
|
||||
} else
|
||||
register_use(tvar, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1583,6 +1583,9 @@ typedef struct Scheme_IR_Local
|
|||
struct {
|
||||
/* Constant- and copy-propagation information: */
|
||||
Scheme_Object *known_val;
|
||||
/* Whether `known_val` must be cleared when the variable's
|
||||
only use is duplicated: */
|
||||
int clear_known_on_multi_use;
|
||||
/* Number of `lambda` wrappers, which is relevant for
|
||||
accumulating closures, etc.: */
|
||||
int lambda_depth;
|
||||
|
@ -3300,7 +3303,6 @@ Scheme_Object *scheme_optimize_expr(Scheme_Object *, Optimize_Info *, int contex
|
|||
#define scheme_optimize_tail_context(c) scheme_optimize_result_context(c)
|
||||
|
||||
int scheme_ir_duplicate_ok(Scheme_Object *o, int cross_mod);
|
||||
int scheme_ir_propagate_ok(Scheme_Object *o, Optimize_Info *info);
|
||||
int scheme_is_statically_proc(Scheme_Object *value, Optimize_Info *info, int flags);
|
||||
XFORM_NONGCING int scheme_predicate_to_local_type(Scheme_Object *pred);
|
||||
Scheme_Object *scheme_make_noninline_proc(Scheme_Object *e);
|
||||
|
|
Loading…
Reference in New Issue
Block a user