8
8
"fmt"
9
9
"go/constant"
10
10
"go/token"
11
+ "os"
11
12
12
13
"cmd/compile/internal/base"
13
14
"cmd/compile/internal/ir"
@@ -16,6 +17,7 @@ import (
16
17
"cmd/compile/internal/typecheck"
17
18
"cmd/compile/internal/types"
18
19
"cmd/internal/obj"
20
+ "cmd/internal/objabi"
19
21
"cmd/internal/src"
20
22
)
21
23
@@ -55,6 +57,28 @@ func (s *Schedule) StaticInit(n ir.Node) {
55
57
}
56
58
}
57
59
60
+ // varToMapInit holds book-keeping state for global map initialization;
61
+ // it records the init function created by the compiler to host the
62
+ // initialization code for the map in question.
63
+ var varToMapInit map [* ir.Name ]* ir.Func
64
+
65
+ // MapInitToVar is the inverse of VarToMapInit; it maintains a mapping
66
+ // from a compiler-generated init function to the map the function is
67
+ // initializing.
68
+ var MapInitToVar map [* ir.Func ]* ir.Name
69
+
70
+ // recordFuncForVar establishes a mapping between global map var "v" and
71
+ // outlined init function "fn" (and vice versa); so that we can use
72
+ // the mappings later on to update relocations.
73
+ func recordFuncForVar (v * ir.Name , fn * ir.Func ) {
74
+ if varToMapInit == nil {
75
+ varToMapInit = make (map [* ir.Name ]* ir.Func )
76
+ MapInitToVar = make (map [* ir.Func ]* ir.Name )
77
+ }
78
+ varToMapInit [v ] = fn
79
+ MapInitToVar [fn ] = v
80
+ }
81
+
58
82
// tryStaticInit attempts to statically execute an initialization
59
83
// statement and reports whether it succeeded.
60
84
func (s * Schedule ) tryStaticInit (nn ir.Node ) bool {
@@ -887,3 +911,162 @@ func truncate(c *ir.ConstExpr, t *types.Type) (*ir.ConstExpr, bool) {
887
911
c .SetType (t )
888
912
return c , true
889
913
}
914
+
915
+ const wrapGlobalMapInitSizeThreshold = 20
916
+
917
+ // tryWrapGlobalMapInit examines the node 'n' to see if it is a map
918
+ // variable initialization, and if so, possibly returns the mapvar
919
+ // being assigned, a new function containing the init code, and a call
920
+ // to the function passing the mapvar. Returns will be nil if the
921
+ // assignment is not to a map, or the map init is not big enough,
922
+ // or if the expression being assigned to the map has side effects.
923
+ func tryWrapGlobalMapInit (n ir.Node ) (mapvar * ir.Name , genfn * ir.Func , call ir.Node ) {
924
+ // Look for "X = ..." where X has map type.
925
+ // FIXME: might also be worth trying to look for cases where
926
+ // the LHS is of interface type but RHS is map type.
927
+ if n .Op () != ir .OAS {
928
+ return nil , nil , nil
929
+ }
930
+ as := n .(* ir.AssignStmt )
931
+ if ir .IsBlank (as .X ) || as .X .Op () != ir .ONAME {
932
+ return nil , nil , nil
933
+ }
934
+ nm := as .X .(* ir.Name )
935
+ if ! nm .Type ().IsMap () {
936
+ return nil , nil , nil
937
+ }
938
+
939
+ // Determine size of RHS.
940
+ rsiz := 0
941
+ ir .Any (as .Y , func (n ir.Node ) bool {
942
+ rsiz ++
943
+ return false
944
+ })
945
+ if base .Debug .WrapGlobalMapDbg > 0 {
946
+ fmt .Fprintf (os .Stderr , "=-= mapassign %s %v rhs size %d\n " ,
947
+ base .Ctxt .Pkgpath , n , rsiz )
948
+ }
949
+
950
+ // Reject smaller candidates if not in stress mode.
951
+ if rsiz < wrapGlobalMapInitSizeThreshold && base .Debug .WrapGlobalMapStress == 0 {
952
+ if base .Debug .WrapGlobalMapDbg > 1 {
953
+ fmt .Fprintf (os .Stderr , "=-= skipping %v size too small at %d\n " ,
954
+ nm , rsiz )
955
+ }
956
+ return nil , nil , nil
957
+ }
958
+
959
+ // Reject right hand sides with side effects.
960
+ if AnySideEffects (as .Y ) {
961
+ if base .Debug .WrapGlobalMapDbg > 0 {
962
+ fmt .Fprintf (os .Stderr , "=-= rejected %v due to side effects\n " , nm )
963
+ }
964
+ return nil , nil , nil
965
+ }
966
+
967
+ if base .Debug .WrapGlobalMapDbg > 1 {
968
+ fmt .Fprintf (os .Stderr , "=-= committed for: %+v\n " , n )
969
+ }
970
+
971
+ // Create a new function that will (eventually) have this form:
972
+ //
973
+ // func map.init.%d() {
974
+ // globmapvar = <map initialization>
975
+ // }
976
+ //
977
+ minitsym := typecheck .LookupNum ("map.init." , mapinitgen )
978
+ mapinitgen ++
979
+ newfn := typecheck .DeclFunc (minitsym , nil , nil , nil )
980
+ if base .Debug .WrapGlobalMapDbg > 0 {
981
+ fmt .Fprintf (os .Stderr , "=-= generated func is %v\n " , newfn )
982
+ }
983
+
984
+ // NB: we're relying on this phase being run before inlining;
985
+ // if for some reason we need to move it after inlining, we'll
986
+ // need code here that relocates or duplicates inline temps.
987
+
988
+ // Insert assignment into function body; mark body finished.
989
+ newfn .Body = append (newfn .Body , as )
990
+ typecheck .FinishFuncBody ()
991
+
992
+ typecheck .Func (newfn )
993
+
994
+ const no = `
995
+ // Register new function with decls.
996
+ typecheck.Target.Decls = append(typecheck.Target.Decls, newfn)
997
+ `
998
+
999
+ // Create call to function, passing mapvar.
1000
+ fncall := ir .NewCallExpr (n .Pos (), ir .OCALL , newfn .Nname , nil )
1001
+
1002
+ if base .Debug .WrapGlobalMapDbg > 1 {
1003
+ fmt .Fprintf (os .Stderr , "=-= mapvar is %v\n " , nm )
1004
+ fmt .Fprintf (os .Stderr , "=-= newfunc is %+v\n " , newfn )
1005
+ fmt .Fprintf (os .Stderr , "=-= call is %+v\n " , fncall )
1006
+ }
1007
+
1008
+ return nm , newfn , typecheck .Stmt (fncall )
1009
+ }
1010
+
1011
+ // mapinitgen is a counter used to uniquify compiler-generated
1012
+ // map init functions.
1013
+ var mapinitgen int
1014
+
1015
+ // AddKeepRelocations adds a dummy "R_KEEP" relocation from each
1016
+ // global map variable V to its associated outlined init function.
1017
+ // These relocation ensure that if the map var itself is determined to
1018
+ // be reachable at link time, we also mark the init function as
1019
+ // reachable.
1020
+ func AddKeepRelocations () {
1021
+ if varToMapInit == nil {
1022
+ return
1023
+ }
1024
+ for k , v := range varToMapInit {
1025
+ // Add R_KEEP relocation from map to init function.
1026
+ fs := v .Linksym ()
1027
+ if fs == nil {
1028
+ base .Fatalf ("bad: func %v has no linksym" , v )
1029
+ }
1030
+ vs := k .Linksym ()
1031
+ if vs == nil {
1032
+ base .Fatalf ("bad: mapvar %v has no linksym" , k )
1033
+ }
1034
+ r := obj .Addrel (vs )
1035
+ r .Sym = fs
1036
+ r .Type = objabi .R_KEEP
1037
+ if base .Debug .WrapGlobalMapDbg > 1 {
1038
+ fmt .Fprintf (os .Stderr , "=-= add R_KEEP relo from %s to %s\n " ,
1039
+ vs .Name , fs .Name )
1040
+ }
1041
+ }
1042
+ varToMapInit = nil
1043
+ }
1044
+
1045
+ // OutlineMapInits walks through a list of init statements (candidates
1046
+ // for inclusion in the package "init" function) and returns an
1047
+ // updated list in which items corresponding to map variable
1048
+ // initializations have been replaced with calls to outline "map init"
1049
+ // functions (if legal/profitable). Return value is an updated list
1050
+ // and a list of any newly generated "map init" functions.
1051
+ func OutlineMapInits (stmts []ir.Node ) ([]ir.Node , []* ir.Func ) {
1052
+ if ! base .Flag .WrapGlobalMapInit {
1053
+ return stmts , nil
1054
+ }
1055
+ newfuncs := []* ir.Func {}
1056
+ for i := range stmts {
1057
+ s := stmts [i ]
1058
+ // Call the helper tryWrapGlobalMapInit to see if the LHS of
1059
+ // this assignment is to a map var, and if so whether the RHS
1060
+ // should be outlined into a separate init function. If the
1061
+ // outline goes through, then replace the original init
1062
+ // statement with the call to the outlined func, and append
1063
+ // the new outlined func to our return list.
1064
+ if mapvar , genfn , call := tryWrapGlobalMapInit (s ); call != nil {
1065
+ stmts [i ] = call
1066
+ newfuncs = append (newfuncs , genfn )
1067
+ recordFuncForVar (mapvar , genfn )
1068
+ }
1069
+ }
1070
+
1071
+ return stmts , newfuncs
1072
+ }
0 commit comments