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