317 lines
8.3 KiB
Factor
317 lines
8.3 KiB
Factor
! Copyright (C) 2008 Slava Pestov.
|
|
! See http://factorcode.org/license.txt for BSD license.
|
|
USING: kernel math namespaces assocs hashtables sequences arrays
|
|
accessors vectors combinators sets classes compiler.cfg
|
|
compiler.cfg.registers compiler.cfg.instructions
|
|
compiler.cfg.copy-prop ;
|
|
IN: compiler.cfg.alias-analysis
|
|
|
|
! Alias analysis -- assumes compiler.cfg.height has already run.
|
|
!
|
|
! We try to eliminate redundant slot and stack
|
|
! traffic using some simple heuristics.
|
|
!
|
|
! All heap-allocated objects which are loaded from the stack, or
|
|
! other object slots are pessimistically assumed to belong to
|
|
! the same alias class.
|
|
!
|
|
! Freshly-allocated objects get their own alias class.
|
|
!
|
|
! The data and retain stack pointer registers are treated
|
|
! uniformly, and each one gets its own alias class.
|
|
!
|
|
! Simple pseudo-C example showing load elimination:
|
|
!
|
|
! int *x, *y, z: inputs
|
|
! int a, b, c, d, e: locals
|
|
!
|
|
! Before alias analysis:
|
|
!
|
|
! a = x[2]
|
|
! b = x[2]
|
|
! c = x[3]
|
|
! y[2] = z
|
|
! d = x[2]
|
|
! e = y[2]
|
|
! f = x[3]
|
|
!
|
|
! After alias analysis:
|
|
!
|
|
! a = x[2]
|
|
! b = a /* ELIMINATED */
|
|
! c = x[3]
|
|
! y[2] = z
|
|
! d = x[2] /* if x=y, d=z, if x!=y, d=b; NOT ELIMINATED */
|
|
! e = z /* ELIMINATED */
|
|
! f = c /* ELIMINATED */
|
|
!
|
|
! Simple pseudo-C example showing store elimination:
|
|
!
|
|
! Before alias analysis:
|
|
!
|
|
! x[0] = a
|
|
! b = x[n]
|
|
! x[0] = c
|
|
! x[1] = d
|
|
! e = x[0]
|
|
! x[1] = c
|
|
!
|
|
! After alias analysis:
|
|
!
|
|
! x[0] = a /* dead if n = 0, live otherwise; NOT ELIMINATED */
|
|
! b = x[n]
|
|
! x[0] = c
|
|
! /* x[1] = d */ /* ELIMINATED */
|
|
! e = c
|
|
! x[1] = c
|
|
|
|
! Map vregs -> alias classes
|
|
SYMBOL: vregs>acs
|
|
|
|
: check [ "BUG: static type error detected" throw ] unless* ; inline
|
|
|
|
: vreg>ac ( vreg -- ac )
|
|
#! Only vregs produced by ##allot, ##peek and ##slot can
|
|
#! ever be used as valid inputs to ##slot and ##set-slot,
|
|
#! so we assert this fact by not giving alias classes to
|
|
#! other vregs.
|
|
vregs>acs get at check ;
|
|
|
|
! Map alias classes -> sequence of vregs
|
|
SYMBOL: acs>vregs
|
|
|
|
: ac>vregs ( ac -- vregs ) acs>vregs get at ;
|
|
|
|
: aliases ( vreg -- vregs )
|
|
#! All vregs which may contain the same value as vreg.
|
|
vreg>ac ac>vregs ;
|
|
|
|
: each-alias ( vreg quot -- )
|
|
[ aliases ] dip each ; inline
|
|
|
|
! Map vregs -> slot# -> vreg
|
|
SYMBOL: live-slots
|
|
|
|
! Current instruction number
|
|
SYMBOL: insn#
|
|
|
|
! Load/store history, for dead store elimination
|
|
TUPLE: load insn# ;
|
|
TUPLE: store insn# ;
|
|
|
|
: new-action ( class -- action )
|
|
insn# get swap boa ; inline
|
|
|
|
! Maps vreg -> slot# -> sequence of loads/stores
|
|
SYMBOL: histories
|
|
|
|
: history ( vreg -- history ) histories get at ;
|
|
|
|
: set-ac ( vreg ac -- )
|
|
#! Set alias class of newly-seen vreg.
|
|
{
|
|
[ drop H{ } clone swap histories get set-at ]
|
|
[ drop H{ } clone swap live-slots get set-at ]
|
|
[ swap vregs>acs get set-at ]
|
|
[ acs>vregs get push-at ]
|
|
} 2cleave ;
|
|
|
|
: live-slot ( slot#/f vreg -- vreg' )
|
|
#! If the slot number is unknown, we never reuse a previous
|
|
#! value.
|
|
over [ live-slots get at at ] [ 2drop f ] if ;
|
|
|
|
: load-constant-slot ( value slot# vreg -- )
|
|
live-slots get at check set-at ;
|
|
|
|
: load-slot ( value slot#/f vreg -- )
|
|
over [ load-constant-slot ] [ 3drop ] if ;
|
|
|
|
: record-constant-slot ( slot# vreg -- )
|
|
#! A load can potentially read every store of this slot#
|
|
#! in that alias class.
|
|
[
|
|
history [ load new-action swap ?push ] change-at
|
|
] with each-alias ;
|
|
|
|
: record-computed-slot ( vreg -- )
|
|
#! Computed load is like a load of every slot touched so far
|
|
[
|
|
history values [ load new-action swap push ] each
|
|
] each-alias ;
|
|
|
|
: remember-slot ( value slot#/f vreg -- )
|
|
over
|
|
[ [ record-constant-slot ] [ load-constant-slot ] 2bi ]
|
|
[ 2nip record-computed-slot ] if ;
|
|
|
|
SYMBOL: ac-counter
|
|
|
|
: next-ac ( -- n )
|
|
ac-counter [ dup 1+ ] change ;
|
|
|
|
! Alias class for objects which are loaded from the data stack
|
|
! or other object slots. We pessimistically assume that they
|
|
! can all alias each other.
|
|
SYMBOL: heap-ac
|
|
|
|
: set-heap-ac ( vreg -- ) heap-ac get set-ac ;
|
|
|
|
: set-new-ac ( vreg -- ) next-ac set-ac ;
|
|
|
|
: kill-constant-set-slot ( slot# vreg -- )
|
|
[ live-slots get at delete-at ] with each-alias ;
|
|
|
|
: record-constant-set-slot ( slot# vreg -- )
|
|
history [
|
|
dup empty? [ dup peek store? [ dup pop* ] when ] unless
|
|
store new-action swap ?push
|
|
] change-at ;
|
|
|
|
: kill-computed-set-slot ( ac -- )
|
|
[ live-slots get at clear-assoc ] each-alias ;
|
|
|
|
: remember-set-slot ( slot#/f vreg -- )
|
|
over [
|
|
[ record-constant-set-slot ]
|
|
[ kill-constant-set-slot ] 2bi
|
|
] [ nip kill-computed-set-slot ] if ;
|
|
|
|
SYMBOL: constants
|
|
|
|
: constant ( vreg -- n/f )
|
|
#! Return a ##load-immediate value, or f if the vreg was not
|
|
#! assigned by an ##load-immediate.
|
|
resolve constants get at ;
|
|
|
|
! We treat slot accessors and stack traffic alike
|
|
GENERIC: insn-slot# ( insn -- slot#/f )
|
|
GENERIC: insn-object ( insn -- vreg )
|
|
|
|
M: ##peek insn-slot# loc>> n>> ;
|
|
M: ##replace insn-slot# loc>> n>> ;
|
|
M: ##slot insn-slot# slot>> constant ;
|
|
M: ##slot-imm insn-slot# slot>> ;
|
|
M: ##set-slot insn-slot# slot>> constant ;
|
|
M: ##set-slot-imm insn-slot# slot>> ;
|
|
M: ##alien-global insn-slot# [ library>> ] [ symbol>> ] bi 2array ;
|
|
|
|
M: ##peek insn-object loc>> class ;
|
|
M: ##replace insn-object loc>> class ;
|
|
M: ##slot insn-object obj>> resolve ;
|
|
M: ##slot-imm insn-object obj>> resolve ;
|
|
M: ##set-slot insn-object obj>> resolve ;
|
|
M: ##set-slot-imm insn-object obj>> resolve ;
|
|
M: ##alien-global insn-object drop \ ##alien-global ;
|
|
|
|
: init-alias-analysis ( -- )
|
|
H{ } clone histories set
|
|
H{ } clone vregs>acs set
|
|
H{ } clone acs>vregs set
|
|
H{ } clone live-slots set
|
|
H{ } clone constants set
|
|
H{ } clone copies set
|
|
|
|
0 ac-counter set
|
|
next-ac heap-ac set
|
|
|
|
ds-loc next-ac set-ac
|
|
rs-loc next-ac set-ac ;
|
|
|
|
GENERIC: analyze-aliases* ( insn -- insn' )
|
|
|
|
M: ##load-immediate analyze-aliases*
|
|
dup [ val>> ] [ dst>> ] bi constants get set-at ;
|
|
|
|
M: ##load-indirect analyze-aliases*
|
|
dup dst>> set-heap-ac ;
|
|
|
|
M: ##alien-global analyze-aliases*
|
|
dup dst>> set-heap-ac ;
|
|
|
|
M: ##allot analyze-aliases*
|
|
#! A freshly allocated object is distinct from any other
|
|
#! object.
|
|
dup dst>> set-new-ac ;
|
|
|
|
M: ##box-float analyze-aliases*
|
|
#! A freshly allocated object is distinct from any other
|
|
#! object.
|
|
dup dst>> set-new-ac ;
|
|
|
|
M: ##box-alien analyze-aliases*
|
|
#! A freshly allocated object is distinct from any other
|
|
#! object.
|
|
dup dst>> set-new-ac ;
|
|
|
|
M: ##read analyze-aliases*
|
|
dup dst>> set-heap-ac
|
|
dup [ dst>> ] [ insn-slot# ] [ insn-object ] tri
|
|
2dup live-slot dup [
|
|
2nip f \ ##copy boa analyze-aliases* nip
|
|
] [
|
|
drop remember-slot
|
|
] if ;
|
|
|
|
: idempotent? ( value slot#/f vreg -- ? )
|
|
#! Are we storing a value back to the same slot it was read
|
|
#! from?
|
|
live-slot = ;
|
|
|
|
M: ##write analyze-aliases*
|
|
dup
|
|
[ src>> resolve ] [ insn-slot# ] [ insn-object ] tri
|
|
[ remember-set-slot drop ] [ load-slot ] 3bi ;
|
|
|
|
M: ##copy analyze-aliases*
|
|
#! The output vreg gets the same alias class as the input
|
|
#! vreg, since they both contain the same value.
|
|
dup record-copy ;
|
|
|
|
M: insn analyze-aliases* ;
|
|
|
|
: analyze-aliases ( insns -- insns' )
|
|
[ insn# set analyze-aliases* ] map-index sift ;
|
|
|
|
SYMBOL: live-stores
|
|
|
|
: compute-live-stores ( -- )
|
|
histories get
|
|
values [
|
|
values [ [ store? ] filter [ insn#>> ] map ] map concat
|
|
] map concat unique
|
|
live-stores set ;
|
|
|
|
GENERIC: eliminate-dead-stores* ( insn -- insn' )
|
|
|
|
: (eliminate-dead-stores) ( insn -- insn' )
|
|
dup insn-slot# [
|
|
insn# get live-stores get key? [
|
|
drop f
|
|
] unless
|
|
] when ;
|
|
|
|
M: ##replace eliminate-dead-stores*
|
|
#! Writes to above the top of the stack can be pruned also.
|
|
#! This is sound since any such writes are not observable
|
|
#! after the basic block, and any reads of those locations
|
|
#! will have been converted to copies by analyze-slot,
|
|
#! and the final stack height of the basic block is set at
|
|
#! the beginning by compiler.cfg.stack.
|
|
dup loc>> n>> 0 < [ drop f ] [ (eliminate-dead-stores) ] if ;
|
|
|
|
M: ##set-slot eliminate-dead-stores* (eliminate-dead-stores) ;
|
|
|
|
M: ##set-slot-imm eliminate-dead-stores* (eliminate-dead-stores) ;
|
|
|
|
M: insn eliminate-dead-stores* ;
|
|
|
|
: eliminate-dead-stores ( insns -- insns' )
|
|
[ insn# set eliminate-dead-stores* ] map-index sift ;
|
|
|
|
: alias-analysis ( insns -- insns' )
|
|
init-alias-analysis
|
|
analyze-aliases
|
|
compute-live-stores
|
|
eliminate-dead-stores ;
|