From 2c6df2e35dd95621d025306a49b863efd77ebf3a Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Fri, 22 May 2020 15:22:52 -0400 Subject: cmd/compile: reject misplaced go:build comments We are converting from using error-prone ad-hoc syntax // +build lines to less error-prone, standard boolean syntax //go:build lines. The timeline is: Go 1.16: prepare for transition - Builds still use // +build for file selection. - Source files may not contain //go:build without // +build. - Builds fail when a source file contains //go:build lines without // +build lines. <<< Go 1.17: start transition - Builds prefer //go:build for file selection, falling back to // +build for files containing only // +build. - Source files may contain //go:build without // +build (but they won't build with Go 1.16). - Gofmt moves //go:build and // +build lines to proper file locations. - Gofmt introduces //go:build lines into files with only // +build lines. - Go vet rejects files with mismatched //go:build and // +build lines. Go 1.18: complete transition - Go fix removes // +build lines, leaving behind equivalent // +build lines. This CL provides part of the <<< marked line above in the Go 1.16 step: rejecting files containing //go:build but not // +build. The standard go command checks only consider the top of the file. This compiler check, along with a separate go vet check for ignored files, handles the remainder of the file. For #41184. Change-Id: I014006eebfc84ab5943de18bc90449e534f150a2 Reviewed-on: https://go-review.googlesource.com/c/go/+/240601 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Go Bot Reviewed-by: Ian Lance Taylor --- src/cmd/compile/internal/gc/noder.go | 1 + 1 file changed, 1 insertion(+) (limited to 'src/cmd/compile/internal/gc/noder.go') diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 5dce533e4b..c63c80dd36 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -242,6 +242,7 @@ func (p *noder) node() { mkpackage(p.file.PkgName.Value) if pragma, ok := p.file.Pragma.(*Pragma); ok { + pragma.Flag &^= GoBuildPragma p.checkUnused(pragma) } -- cgit v1.3 From e2931612b04e2ea6be337872c6f4a31c7d7dec54 Mon Sep 17 00:00:00 2001 From: Alberto Donizetti Date: Mon, 12 Oct 2020 15:02:59 +0200 Subject: cmd/compile: rename strlit, Bool, and Int64 *Node accessors The Node type has shortcuts to access bool and int Values: func (n *Node) Int64() int64 for n.Val().U.(*Mpint).Int64() func (n *Node) Bool() bool for n.Val().U.(bool) I was convinced we didn't have one for string literal nodes, until I noticed that we do, it's just called strlit, it's not a method, and it's later in the file: func strlit(n *Node) string This change, for consistency: - Renames strlit to StringVal and makes it a *Node method - Renames Bool and Int64 to BoolVal and Int64Val - Moves StringVal near the other two Change-Id: I18e635384c35eb3a238fd52b1ccd322b1a74d733 Reviewed-on: https://go-review.googlesource.com/c/go/+/261361 Trust: Alberto Donizetti Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/gc/const.go | 34 ++++++++++++++++++-------------- src/cmd/compile/internal/gc/esc.go | 2 +- src/cmd/compile/internal/gc/noder.go | 6 +++--- src/cmd/compile/internal/gc/obj.go | 2 +- src/cmd/compile/internal/gc/order.go | 4 ++-- src/cmd/compile/internal/gc/sinit.go | 14 ++++++------- src/cmd/compile/internal/gc/ssa.go | 8 ++++---- src/cmd/compile/internal/gc/swt.go | 10 +++++----- src/cmd/compile/internal/gc/typecheck.go | 31 ++++++++++++++--------------- src/cmd/compile/internal/gc/walk.go | 30 ++++++++++++++-------------- 10 files changed, 72 insertions(+), 69 deletions(-) (limited to 'src/cmd/compile/internal/gc/noder.go') diff --git a/src/cmd/compile/internal/gc/const.go b/src/cmd/compile/internal/gc/const.go index b28c0fc8d0..b92c8d66b5 100644 --- a/src/cmd/compile/internal/gc/const.go +++ b/src/cmd/compile/internal/gc/const.go @@ -114,16 +114,16 @@ func (v Val) Interface() interface{} { type NilVal struct{} -// Int64 returns n as an int64. +// Int64Val returns n as an int64. // n must be an integer or rune constant. -func (n *Node) Int64() int64 { +func (n *Node) Int64Val() int64 { if !Isconst(n, CTINT) { - Fatalf("Int64(%v)", n) + Fatalf("Int64Val(%v)", n) } return n.Val().U.(*Mpint).Int64() } -// CanInt64 reports whether it is safe to call Int64() on n. +// CanInt64 reports whether it is safe to call Int64Val() on n. func (n *Node) CanInt64() bool { if !Isconst(n, CTINT) { return false @@ -131,18 +131,27 @@ func (n *Node) CanInt64() bool { // if the value inside n cannot be represented as an int64, the // return value of Int64 is undefined - return n.Val().U.(*Mpint).CmpInt64(n.Int64()) == 0 + return n.Val().U.(*Mpint).CmpInt64(n.Int64Val()) == 0 } -// Bool returns n as a bool. +// BoolVal returns n as a bool. // n must be a boolean constant. -func (n *Node) Bool() bool { +func (n *Node) BoolVal() bool { if !Isconst(n, CTBOOL) { - Fatalf("Bool(%v)", n) + Fatalf("BoolVal(%v)", n) } return n.Val().U.(bool) } +// StringVal returns the value of a literal string Node as a string. +// n must be a string constant. +func (n *Node) StringVal() string { + if !Isconst(n, CTSTR) { + Fatalf("StringVal(%v)", n) + } + return n.Val().U.(string) +} + // truncate float literal fv to 32-bit or 64-bit precision // according to type; return truncated value. func truncfltlit(oldv *Mpflt, t *types.Type) *Mpflt { @@ -612,7 +621,7 @@ func evconst(n *Node) { var strs []string i2 := i1 for i2 < len(s) && Isconst(s[i2], CTSTR) { - strs = append(strs, strlit(s[i2])) + strs = append(strs, s[i2].StringVal()) i2++ } @@ -635,7 +644,7 @@ func evconst(n *Node) { switch nl.Type.Etype { case TSTRING: if Isconst(nl, CTSTR) { - setintconst(n, int64(len(strlit(nl)))) + setintconst(n, int64(len(nl.StringVal()))) } case TARRAY: if !hascallchan(nl) { @@ -1129,11 +1138,6 @@ func defaultType(t *types.Type) *types.Type { return nil } -// strlit returns the value of a literal string Node as a string. -func strlit(n *Node) string { - return n.Val().U.(string) -} - func smallintconst(n *Node) bool { if n.Op == OLITERAL && Isconst(n, CTINT) && n.Type != nil { switch simtype[n.Type.Etype] { diff --git a/src/cmd/compile/internal/gc/esc.go b/src/cmd/compile/internal/gc/esc.go index d7aa72b450..c11066a62f 100644 --- a/src/cmd/compile/internal/gc/esc.go +++ b/src/cmd/compile/internal/gc/esc.go @@ -204,7 +204,7 @@ func heapAllocReason(n *Node) string { if !smallintconst(r) { return "non-constant size" } - if t := n.Type; t.Elem().Width != 0 && r.Int64() >= maxImplicitStackVarSize/t.Elem().Width { + if t := n.Type; t.Elem().Width != 0 && r.Int64Val() >= maxImplicitStackVarSize/t.Elem().Width { return "too large for stack" } } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index c63c80dd36..68d0327cdb 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -774,7 +774,7 @@ func (p *noder) sum(x syntax.Expr) *Node { n := p.expr(x) if Isconst(n, CTSTR) && n.Sym == nil { nstr = n - chunks = append(chunks, strlit(nstr)) + chunks = append(chunks, nstr.StringVal()) } for i := len(adds) - 1; i >= 0; i-- { @@ -784,12 +784,12 @@ func (p *noder) sum(x syntax.Expr) *Node { if Isconst(r, CTSTR) && r.Sym == nil { if nstr != nil { // Collapse r into nstr instead of adding to n. - chunks = append(chunks, strlit(r)) + chunks = append(chunks, r.StringVal()) continue } nstr = r - chunks = append(chunks, strlit(nstr)) + chunks = append(chunks, nstr.StringVal()) } else { if len(chunks) > 1 { nstr.SetVal(Val{U: strings.Join(chunks, "")}) diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index b55331a948..d7f4a94041 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -272,7 +272,7 @@ func dumpGlobalConst(n *Node) { default: return } - Ctxt.DwarfIntConst(myimportpath, n.Sym.Name, typesymname(t), n.Int64()) + Ctxt.DwarfIntConst(myimportpath, n.Sym.Name, typesymname(t), n.Int64Val()) } func dumpglobls() { diff --git a/src/cmd/compile/internal/gc/order.go b/src/cmd/compile/internal/gc/order.go index 75da154fe2..e562ab7556 100644 --- a/src/cmd/compile/internal/gc/order.go +++ b/src/cmd/compile/internal/gc/order.go @@ -1102,7 +1102,7 @@ func (o *Order) expr(n, lhs *Node) *Node { haslit := false for _, n1 := range n.List.Slice() { hasbyte = hasbyte || n1.Op == OBYTES2STR - haslit = haslit || n1.Op == OLITERAL && len(strlit(n1)) != 0 + haslit = haslit || n1.Op == OLITERAL && len(n1.StringVal()) != 0 } if haslit && hasbyte { @@ -1274,7 +1274,7 @@ func (o *Order) expr(n, lhs *Node) *Node { var t *types.Type switch n.Op { case OSLICELIT: - t = types.NewArray(n.Type.Elem(), n.Right.Int64()) + t = types.NewArray(n.Type.Elem(), n.Right.Int64Val()) case OCALLPART: t = partialCallType(n) } diff --git a/src/cmd/compile/internal/gc/sinit.go b/src/cmd/compile/internal/gc/sinit.go index af19a96bbc..fda33534b6 100644 --- a/src/cmd/compile/internal/gc/sinit.go +++ b/src/cmd/compile/internal/gc/sinit.go @@ -128,7 +128,7 @@ func (s *InitSchedule) staticcopy(l *Node, r *Node) bool { case OSLICELIT: // copy slice a := s.inittemps[r] - slicesym(l, a, r.Right.Int64()) + slicesym(l, a, r.Right.Int64Val()) return true case OARRAYLIT, OSTRUCTLIT: @@ -205,7 +205,7 @@ func (s *InitSchedule) staticassign(l *Node, r *Node) bool { case OSTR2BYTES: if l.Class() == PEXTERN && r.Left.Op == OLITERAL { - sval := strlit(r.Left) + sval := r.Left.StringVal() slicebytes(l, sval) return true } @@ -213,7 +213,7 @@ func (s *InitSchedule) staticassign(l *Node, r *Node) bool { case OSLICELIT: s.initplan(r) // Init slice. - bound := r.Right.Int64() + bound := r.Right.Int64Val() ta := types.NewArray(r.Type.Elem(), bound) ta.SetNoalg(true) a := staticname(ta) @@ -413,7 +413,7 @@ func getdyn(n *Node, top bool) initGenType { if !top { return initDynamic } - if n.Right.Int64()/4 > int64(n.List.Len()) { + if n.Right.Int64Val()/4 > int64(n.List.Len()) { // <25% of entries have explicit values. // Very rough estimation, it takes 4 bytes of instructions // to initialize 1 byte of result. So don't use a static @@ -589,12 +589,12 @@ func isSmallSliceLit(n *Node) bool { r := n.Right - return smallintconst(r) && (n.Type.Elem().Width == 0 || r.Int64() <= smallArrayBytes/n.Type.Elem().Width) + return smallintconst(r) && (n.Type.Elem().Width == 0 || r.Int64Val() <= smallArrayBytes/n.Type.Elem().Width) } func slicelit(ctxt initContext, n *Node, var_ *Node, init *Nodes) { // make an array type corresponding the number of elements we have - t := types.NewArray(n.Type.Elem(), n.Right.Int64()) + t := types.NewArray(n.Type.Elem(), n.Right.Int64Val()) dowidth(t) if ctxt == inNonInitFunction { @@ -993,7 +993,7 @@ func oaslit(n *Node, init *Nodes) bool { func getlit(lit *Node) int { if smallintconst(lit) { - return int(lit.Int64()) + return int(lit.Int64Val()) } return -1 } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index e363f4f723..3d5fa4cd0a 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -1271,7 +1271,7 @@ func (s *state) stmt(n *Node) { // We're assigning a slicing operation back to its source. // Don't write back fields we aren't changing. See issue #14855. i, j, k := rhs.SliceBounds() - if i != nil && (i.Op == OLITERAL && i.Val().Ctype() == CTINT && i.Int64() == 0) { + if i != nil && (i.Op == OLITERAL && i.Val().Ctype() == CTINT && i.Int64Val() == 0) { // [0:...] is the same as [:...] i = nil } @@ -1301,7 +1301,7 @@ func (s *state) stmt(n *Node) { case OIF: if Isconst(n.Left, CTBOOL) { s.stmtList(n.Left.Ninit) - if n.Left.Bool() { + if n.Left.BoolVal() { s.stmtList(n.Nbody) } else { s.stmtList(n.Rlist) @@ -2610,7 +2610,7 @@ func (s *state) expr(n *Node) *ssa.Value { // Replace "abc"[1] with 'b'. // Delayed until now because "abc"[1] is not an ideal constant. // See test/fixedbugs/issue11370.go. - return s.newValue0I(ssa.OpConst8, types.Types[TUINT8], int64(int8(strlit(n.Left)[n.Right.Int64()]))) + return s.newValue0I(ssa.OpConst8, types.Types[TUINT8], int64(int8(n.Left.StringVal()[n.Right.Int64Val()]))) } a := s.expr(n.Left) i := s.expr(n.Right) @@ -2619,7 +2619,7 @@ func (s *state) expr(n *Node) *ssa.Value { ptrtyp := s.f.Config.Types.BytePtr ptr := s.newValue1(ssa.OpStringPtr, ptrtyp, a) if Isconst(n.Right, CTINT) { - ptr = s.newValue1I(ssa.OpOffPtr, ptrtyp, n.Right.Int64(), ptr) + ptr = s.newValue1I(ssa.OpOffPtr, ptrtyp, n.Right.Int64Val(), ptr) } else { ptr = s.newValue2(ssa.OpAddPtr, ptrtyp, ptr, i) } diff --git a/src/cmd/compile/internal/gc/swt.go b/src/cmd/compile/internal/gc/swt.go index bf0410900f..bfbedb2aa5 100644 --- a/src/cmd/compile/internal/gc/swt.go +++ b/src/cmd/compile/internal/gc/swt.go @@ -358,8 +358,8 @@ func (s *exprSwitch) flush() { // all we need here is consistency. We respect this // sorting below. sort.Slice(cc, func(i, j int) bool { - si := strlit(cc[i].lo) - sj := strlit(cc[j].lo) + si := cc[i].lo.StringVal() + sj := cc[j].lo.StringVal() if len(si) != len(sj) { return len(si) < len(sj) } @@ -368,7 +368,7 @@ func (s *exprSwitch) flush() { // runLen returns the string length associated with a // particular run of exprClauses. - runLen := func(run []exprClause) int64 { return int64(len(strlit(run[0].lo))) } + runLen := func(run []exprClause) int64 { return int64(len(run[0].lo.StringVal())) } // Collapse runs of consecutive strings with the same length. var runs [][]exprClause @@ -405,7 +405,7 @@ func (s *exprSwitch) flush() { merged := cc[:1] for _, c := range cc[1:] { last := &merged[len(merged)-1] - if last.jmp == c.jmp && last.hi.Int64()+1 == c.lo.Int64() { + if last.jmp == c.jmp && last.hi.Int64Val()+1 == c.lo.Int64Val() { last.hi = c.lo } else { merged = append(merged, c) @@ -440,7 +440,7 @@ func (c *exprClause) test(exprname *Node) *Node { // Optimize "switch true { ...}" and "switch false { ... }". if Isconst(exprname, CTBOOL) && !c.lo.Type.IsInterface() { - if exprname.Bool() { + if exprname.BoolVal() { return c.lo } else { return nodl(c.pos, ONOT, c.lo, nil) diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index 769341ee04..75ce95832e 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -1046,13 +1046,13 @@ func typecheck1(n *Node, top int) (res *Node) { } if !n.Bounded() && Isconst(n.Right, CTINT) { - x := n.Right.Int64() + x := n.Right.Int64Val() if x < 0 { yyerror("invalid %s index %v (index must be non-negative)", why, n.Right) } else if t.IsArray() && x >= t.NumElem() { yyerror("invalid array index %v (out of bounds for %d-element array)", n.Right, t.NumElem()) - } else if Isconst(n.Left, CTSTR) && x >= int64(len(strlit(n.Left))) { - yyerror("invalid string index %v (out of bounds for %d-byte string)", n.Right, len(strlit(n.Left))) + } else if Isconst(n.Left, CTSTR) && x >= int64(len(n.Left.StringVal())) { + yyerror("invalid string index %v (out of bounds for %d-byte string)", n.Right, len(n.Left.StringVal())) } else if n.Right.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { yyerror("invalid %s index %v (index too large)", why, n.Right) } @@ -1148,11 +1148,11 @@ func typecheck1(n *Node, top int) (res *Node) { l = defaultlit(l, types.Types[TINT]) c = defaultlit(c, types.Types[TINT]) - if Isconst(l, CTINT) && l.Int64() < 0 { + if Isconst(l, CTINT) && l.Int64Val() < 0 { Fatalf("len for OSLICEHEADER must be non-negative") } - if Isconst(c, CTINT) && c.Int64() < 0 { + if Isconst(c, CTINT) && c.Int64Val() < 0 { Fatalf("cap for OSLICEHEADER must be non-negative") } @@ -1201,7 +1201,7 @@ func typecheck1(n *Node, top int) (res *Node) { if n.Left.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { Fatalf("len for OMAKESLICECOPY too large") } - if n.Left.Int64() < 0 { + if n.Left.Int64Val() < 0 { Fatalf("len for OMAKESLICECOPY must be non-negative") } } @@ -2187,14 +2187,14 @@ func checksliceindex(l *Node, r *Node, tp *types.Type) bool { } if r.Op == OLITERAL { - if r.Int64() < 0 { + if r.Int64Val() < 0 { yyerror("invalid slice index %v (index must be non-negative)", r) return false - } else if tp != nil && tp.NumElem() >= 0 && r.Int64() > tp.NumElem() { + } else if tp != nil && tp.NumElem() >= 0 && r.Int64Val() > tp.NumElem() { yyerror("invalid slice index %v (out of bounds for %d-element array)", r, tp.NumElem()) return false - } else if Isconst(l, CTSTR) && r.Int64() > int64(len(strlit(l))) { - yyerror("invalid slice index %v (out of bounds for %d-byte string)", r, len(strlit(l))) + } else if Isconst(l, CTSTR) && r.Int64Val() > int64(len(l.StringVal())) { + yyerror("invalid slice index %v (out of bounds for %d-byte string)", r, len(l.StringVal())) return false } else if r.Val().U.(*Mpint).Cmp(maxintval[TINT]) > 0 { yyerror("invalid slice index %v (index too large)", r) @@ -3450,9 +3450,8 @@ func stringtoruneslit(n *Node) *Node { } var l []*Node - s := strlit(n.Left) i := 0 - for _, r := range s { + for _, r := range n.Left.StringVal() { l = append(l, nod(OKEY, nodintconst(int64(i)), nodintconst(int64(r)))) i++ } @@ -3904,7 +3903,7 @@ func deadcodefn(fn *Node) { return } case OFOR: - if !Isconst(n.Left, CTBOOL) || n.Left.Bool() { + if !Isconst(n.Left, CTBOOL) || n.Left.BoolVal() { return } default: @@ -3934,7 +3933,7 @@ func deadcodeslice(nn Nodes) { n.Left = deadcodeexpr(n.Left) if Isconst(n.Left, CTBOOL) { var body Nodes - if n.Left.Bool() { + if n.Left.BoolVal() { n.Rlist = Nodes{} body = n.Nbody } else { @@ -3977,7 +3976,7 @@ func deadcodeexpr(n *Node) *Node { n.Left = deadcodeexpr(n.Left) n.Right = deadcodeexpr(n.Right) if Isconst(n.Left, CTBOOL) { - if n.Left.Bool() { + if n.Left.BoolVal() { return n.Right // true && x => x } else { return n.Left // false && x => false @@ -3987,7 +3986,7 @@ func deadcodeexpr(n *Node) *Node { n.Left = deadcodeexpr(n.Left) n.Right = deadcodeexpr(n.Right) if Isconst(n.Left, CTBOOL) { - if n.Left.Bool() { + if n.Left.BoolVal() { return n.Left // true || x => true } else { return n.Right // false || x => x diff --git a/src/cmd/compile/internal/gc/walk.go b/src/cmd/compile/internal/gc/walk.go index 0388662a4f..05a049b3cc 100644 --- a/src/cmd/compile/internal/gc/walk.go +++ b/src/cmd/compile/internal/gc/walk.go @@ -1001,7 +1001,7 @@ opswitch: // The SSA backend will handle those. switch et { case TINT64: - c := n.Right.Int64() + c := n.Right.Int64Val() if c < 0 { c = -c } @@ -1009,7 +1009,7 @@ opswitch: break opswitch } case TUINT64: - c := uint64(n.Right.Int64()) + c := uint64(n.Right.Int64Val()) if c != 0 && c&(c-1) == 0 { break opswitch } @@ -1056,7 +1056,7 @@ opswitch: yyerror("index out of bounds") } } else if Isconst(n.Left, CTSTR) { - n.SetBounded(bounded(r, int64(len(strlit(n.Left))))) + n.SetBounded(bounded(r, int64(len(n.Left.StringVal())))) if Debug['m'] != 0 && n.Bounded() && !Isconst(n.Right, CTINT) { Warn("index bounds check elided") } @@ -1491,7 +1491,7 @@ opswitch: case OSTR2BYTES: s := n.Left if Isconst(s, CTSTR) { - sc := strlit(s) + sc := s.StringVal() // Allocate a [n]byte of the right size. t := types.NewArray(types.Types[TUINT8], int64(len(sc))) @@ -1919,7 +1919,7 @@ func walkprint(nn *Node, init *Nodes) *Node { for i := 0; i < len(s); { var strs []string for i < len(s) && Isconst(s[i], CTSTR) { - strs = append(strs, strlit(s[i])) + strs = append(strs, s[i].StringVal()) i++ } if len(strs) > 0 { @@ -1988,7 +1988,7 @@ func walkprint(nn *Node, init *Nodes) *Node { case TSTRING: cs := "" if Isconst(n, CTSTR) { - cs = strlit(n) + cs = n.StringVal() } switch cs { case " ": @@ -2645,7 +2645,7 @@ func addstr(n *Node, init *Nodes) *Node { sz := int64(0) for _, n1 := range n.List.Slice() { if n1.Op == OLITERAL { - sz += int64(len(strlit(n1))) + sz += int64(len(n1.StringVal())) } } @@ -3439,7 +3439,7 @@ func walkcompare(n *Node, init *Nodes) *Node { func tracecmpArg(n *Node, t *types.Type, init *Nodes) *Node { // Ugly hack to avoid "constant -1 overflows uintptr" errors, etc. - if n.Op == OLITERAL && n.Type.IsSigned() && n.Int64() < 0 { + if n.Op == OLITERAL && n.Type.IsSigned() && n.Int64Val() < 0 { n = copyexpr(n, n.Type, init) } @@ -3509,7 +3509,7 @@ func walkcompareString(n *Node, init *Nodes) *Node { // Length-only checks are ok, though. maxRewriteLen = 0 } - if s := strlit(cs); len(s) <= maxRewriteLen { + if s := cs.StringVal(); len(s) <= maxRewriteLen { if len(s) > 0 { ncs = safeexpr(ncs, init) } @@ -3604,7 +3604,7 @@ func bounded(n *Node, max int64) bool { bits := int32(8 * n.Type.Width) if smallintconst(n) { - v := n.Int64() + v := n.Int64Val() return 0 <= v && v < max } @@ -3612,9 +3612,9 @@ func bounded(n *Node, max int64) bool { case OAND: v := int64(-1) if smallintconst(n.Left) { - v = n.Left.Int64() + v = n.Left.Int64Val() } else if smallintconst(n.Right) { - v = n.Right.Int64() + v = n.Right.Int64Val() } if 0 <= v && v < max { @@ -3623,7 +3623,7 @@ func bounded(n *Node, max int64) bool { case OMOD: if !sign && smallintconst(n.Right) { - v := n.Right.Int64() + v := n.Right.Int64Val() if 0 <= v && v <= max { return true } @@ -3631,7 +3631,7 @@ func bounded(n *Node, max int64) bool { case ODIV: if !sign && smallintconst(n.Right) { - v := n.Right.Int64() + v := n.Right.Int64Val() for bits > 0 && v >= 2 { bits-- v >>= 1 @@ -3640,7 +3640,7 @@ func bounded(n *Node, max int64) bool { case ORSH: if !sign && smallintconst(n.Right) { - v := n.Right.Int64() + v := n.Right.Int64Val() if v > int64(bits) { return true } -- cgit v1.3 From 178b0f59ff0d8be6d41d0f4099d16bfd3bc21ea7 Mon Sep 17 00:00:00 2001 From: hk Date: Mon, 19 Oct 2020 12:13:25 +0000 Subject: cmd/compile/internal/gc: remove an unused method Change-Id: Ib496d1e9a47d2cf69509ffd0009038b7d34a0149 GitHub-Last-Rev: 5b5589dd03e1d781e4cedc643d5e8a7f8bda2139 GitHub-Pull-Request: golang/go#42060 Reviewed-on: https://go-review.googlesource.com/c/go/+/263618 Run-TryBot: Robert Griesemer Reviewed-by: Matthew Dempsky Trust: Matthew Dempsky Trust: Robert Griesemer TryBot-Result: Go Bot --- src/cmd/compile/internal/gc/noder.go | 5 ----- 1 file changed, 5 deletions(-) (limited to 'src/cmd/compile/internal/gc/noder.go') diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 68d0327cdb..85e710086a 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -1438,11 +1438,6 @@ func (p *noder) mkname(name *syntax.Name) *Node { return mkname(p.name(name)) } -func (p *noder) newname(name *syntax.Name) *Node { - // TODO(mdempsky): Set line number? - return newname(p.name(name)) -} - func (p *noder) wrapname(n syntax.Node, x *Node) *Node { // These nodes do not carry line numbers. // Introduce a wrapper node to give them the correct line. -- cgit v1.3 From 8bde9b320e25b2d6edf96fa5e694046fea0c04c8 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 19 Jul 2020 00:32:02 -0400 Subject: cmd/compile: add //go:embed support This commit contains the compiler support for //go:embed lines. The go command passes to the compiler an "embed config" that maps literal patterns like *.txt to the set of files to embed. The compiler then lays out the content of those files as static data in the form of an embed.Files or string or []byte in the final object file. The test for this code is the end-to-end test hooking up the embed, cmd/compile, and cmd/go changes, in the next CL. For #41191. Change-Id: I916e57f8cc65871dc0044c13d3f90c252a3fe1bf Reviewed-on: https://go-review.googlesource.com/c/go/+/243944 Trust: Russ Cox Reviewed-by: Cherry Zhang --- src/cmd/compile/internal/gc/embed.go | 273 +++++++++++++++++++++++++++++++ src/cmd/compile/internal/gc/main.go | 8 +- src/cmd/compile/internal/gc/noder.go | 145 ++++++++++++++-- src/cmd/compile/internal/gc/obj.go | 132 +++++++++++++-- src/cmd/compile/internal/gc/syntax.go | 86 +++++++++- src/cmd/compile/internal/gc/typecheck.go | 8 +- src/cmd/internal/obj/link.go | 29 +++- src/cmd/internal/obj/objfile.go | 49 +++++- 8 files changed, 682 insertions(+), 48 deletions(-) create mode 100644 src/cmd/compile/internal/gc/embed.go (limited to 'src/cmd/compile/internal/gc/noder.go') diff --git a/src/cmd/compile/internal/gc/embed.go b/src/cmd/compile/internal/gc/embed.go new file mode 100644 index 0000000000..103949c1f9 --- /dev/null +++ b/src/cmd/compile/internal/gc/embed.go @@ -0,0 +1,273 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gc + +import ( + "cmd/compile/internal/syntax" + "cmd/compile/internal/types" + "cmd/internal/obj" + "encoding/json" + "io/ioutil" + "log" + "path" + "sort" + "strconv" + "strings" +) + +var embedlist []*Node + +var embedCfg struct { + Patterns map[string][]string + Files map[string]string +} + +func readEmbedCfg(file string) { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-embedcfg: %v", err) + } + if err := json.Unmarshal(data, &embedCfg); err != nil { + log.Fatalf("%s: %v", file, err) + } + if embedCfg.Patterns == nil { + log.Fatalf("%s: invalid embedcfg: missing Patterns", file) + } + if embedCfg.Files == nil { + log.Fatalf("%s: invalid embedcfg: missing Files", file) + } +} + +const ( + embedUnknown = iota + embedBytes + embedString + embedFiles +) + +var numLocalEmbed int + +func varEmbed(p *noder, names []*Node, typ *Node, exprs []*Node, embeds []PragmaEmbed) (newExprs []*Node) { + haveEmbed := false + for _, decl := range p.file.DeclList { + imp, ok := decl.(*syntax.ImportDecl) + if !ok { + // imports always come first + break + } + path, _ := strconv.Unquote(imp.Path.Value) + if path == "embed" { + haveEmbed = true + break + } + } + + pos := embeds[0].Pos + if !haveEmbed { + p.yyerrorpos(pos, "invalid go:embed: missing import \"embed\"") + return exprs + } + if embedCfg.Patterns == nil { + p.yyerrorpos(pos, "invalid go:embed: build system did not supply embed configuration") + return exprs + } + if len(names) > 1 { + p.yyerrorpos(pos, "go:embed cannot apply to multiple vars") + return exprs + } + if len(exprs) > 0 { + p.yyerrorpos(pos, "go:embed cannot apply to var with initializer") + return exprs + } + if typ == nil { + // Should not happen, since len(exprs) == 0 now. + p.yyerrorpos(pos, "go:embed cannot apply to var without type") + return exprs + } + + kind := embedKindApprox(typ) + if kind == embedUnknown { + p.yyerrorpos(pos, "go:embed cannot apply to var of type %v", typ) + return exprs + } + + // Build list of files to store. + have := make(map[string]bool) + var list []string + for _, e := range embeds { + for _, pattern := range e.Patterns { + files, ok := embedCfg.Patterns[pattern] + if !ok { + p.yyerrorpos(e.Pos, "invalid go:embed: build system did not map pattern: %s", pattern) + } + for _, file := range files { + if embedCfg.Files[file] == "" { + p.yyerrorpos(e.Pos, "invalid go:embed: build system did not map file: %s", file) + continue + } + if !have[file] { + have[file] = true + list = append(list, file) + } + if kind == embedFiles { + for dir := path.Dir(file); dir != "." && !have[dir]; dir = path.Dir(dir) { + have[dir] = true + list = append(list, dir+"/") + } + } + } + } + } + sort.Slice(list, func(i, j int) bool { + return embedFileLess(list[i], list[j]) + }) + + if kind == embedString || kind == embedBytes { + if len(list) > 1 { + p.yyerrorpos(pos, "invalid go:embed: multiple files for type %v", typ) + return exprs + } + } + + v := names[0] + if dclcontext != PEXTERN { + numLocalEmbed++ + v = newnamel(v.Pos, lookupN("embed.", numLocalEmbed)) + v.Sym.Def = asTypesNode(v) + v.Name.Param.Ntype = typ + v.SetClass(PEXTERN) + externdcl = append(externdcl, v) + exprs = []*Node{v} + } + + v.Name.Param.SetEmbedFiles(list) + embedlist = append(embedlist, v) + return exprs +} + +// embedKindApprox determines the kind of embedding variable, approximately. +// The match is approximate because we haven't done scope resolution yet and +// can't tell whether "string" and "byte" really mean "string" and "byte". +// The result must be confirmed later, after type checking, using embedKind. +func embedKindApprox(typ *Node) int { + if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && myimportpath == "embed")) { + return embedFiles + } + // These are not guaranteed to match only string and []byte - + // maybe the local package has redefined one of those words. + // But it's the best we can do now during the noder. + // The stricter check happens later, in initEmbed calling embedKind. + if typ.Sym != nil && typ.Sym.Name == "string" && typ.Sym.Pkg == localpkg { + return embedString + } + if typ.Op == OTARRAY && typ.Left == nil && typ.Right.Sym != nil && typ.Right.Sym.Name == "byte" && typ.Right.Sym.Pkg == localpkg { + return embedBytes + } + return embedUnknown +} + +// embedKind determines the kind of embedding variable. +func embedKind(typ *types.Type) int { + if typ.Sym != nil && typ.Sym.Name == "FS" && (typ.Sym.Pkg.Path == "embed" || (typ.Sym.Pkg == localpkg && myimportpath == "embed")) { + return embedFiles + } + if typ == types.Types[TSTRING] { + return embedString + } + if typ.Sym == nil && typ.IsSlice() && typ.Elem() == types.Bytetype { + return embedBytes + } + return embedUnknown +} + +func embedFileNameSplit(name string) (dir, elem string, isDir bool) { + if name[len(name)-1] == '/' { + isDir = true + name = name[:len(name)-1] + } + i := len(name) - 1 + for i >= 0 && name[i] != '/' { + i-- + } + if i < 0 { + return ".", name, isDir + } + return name[:i], name[i+1:], isDir +} + +// embedFileLess implements the sort order for a list of embedded files. +// See the comment inside ../../../../embed/embed.go's Files struct for rationale. +func embedFileLess(x, y string) bool { + xdir, xelem, _ := embedFileNameSplit(x) + ydir, yelem, _ := embedFileNameSplit(y) + return xdir < ydir || xdir == ydir && xelem < yelem +} + +func dumpembeds() { + for _, v := range embedlist { + initEmbed(v) + } +} + +// initEmbed emits the init data for a //go:embed variable, +// which is either a string, a []byte, or an embed.FS. +func initEmbed(v *Node) { + files := v.Name.Param.EmbedFiles() + switch kind := embedKind(v.Type); kind { + case embedUnknown: + yyerrorl(v.Pos, "go:embed cannot apply to var of type %v", v.Type) + + case embedString, embedBytes: + file := files[0] + fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], kind == embedString, nil) + if err != nil { + yyerrorl(v.Pos, "embed %s: %v", file, err) + } + sym := v.Sym.Linksym() + off := 0 + off = dsymptr(sym, off, fsym, 0) // data string + off = duintptr(sym, off, uint64(size)) // len + if kind == embedBytes { + duintptr(sym, off, uint64(size)) // cap for slice + } + + case embedFiles: + slicedata := Ctxt.Lookup(`"".` + v.Sym.Name + `.files`) + off := 0 + // []files pointed at by Files + off = dsymptr(slicedata, off, slicedata, 3*Widthptr) // []file, pointing just past slice + off = duintptr(slicedata, off, uint64(len(files))) + off = duintptr(slicedata, off, uint64(len(files))) + + // embed/embed.go type file is: + // name string + // data string + // hash [16]byte + // Emit one of these per file in the set. + const hashSize = 16 + hash := make([]byte, hashSize) + for _, file := range files { + off = dsymptr(slicedata, off, stringsym(v.Pos, file), 0) // file string + off = duintptr(slicedata, off, uint64(len(file))) + if strings.HasSuffix(file, "/") { + // entry for directory - no data + off = duintptr(slicedata, off, 0) + off = duintptr(slicedata, off, 0) + off += hashSize + } else { + fsym, size, err := fileStringSym(v.Pos, embedCfg.Files[file], true, hash) + if err != nil { + yyerrorl(v.Pos, "embed %s: %v", file, err) + } + off = dsymptr(slicedata, off, fsym, 0) // data string + off = duintptr(slicedata, off, uint64(size)) + off = int(slicedata.WriteBytes(Ctxt, int64(off), hash)) + } + } + ggloblsym(slicedata, int32(off), obj.RODATA|obj.LOCAL) + sym := v.Sym.Linksym() + dsymptr(sym, 0, slicedata, 0) + } +} diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 949755a0e2..4b401f2aa4 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -34,8 +34,6 @@ import ( "strings" ) -var imported_unsafe bool - var ( buildid string spectre string @@ -240,6 +238,7 @@ func Main(archInit func(*Arch)) { flag.BoolVar(&flagDWARF, "dwarf", !Wasm, "generate DWARF symbols") flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", true, "add location lists to DWARF in optimized mode") flag.IntVar(&genDwarfInline, "gendwarfinl", 2, "generate DWARF inline info records") + objabi.Flagfn1("embedcfg", "read go:embed configuration from `file`", readEmbedCfg) objabi.Flagfn1("importmap", "add `definition` of the form source=actual to import map", addImportMap) objabi.Flagfn1("importcfg", "read import configuration from `file`", readImportCfg) flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`") @@ -597,7 +596,7 @@ func Main(archInit func(*Arch)) { timings.Start("fe", "typecheck", "top1") for i := 0; i < len(xtop); i++ { n := xtop[i] - if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) { + if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias()) { xtop[i] = typecheck(n, ctxStmt) } } @@ -609,7 +608,7 @@ func Main(archInit func(*Arch)) { timings.Start("fe", "typecheck", "top2") for i := 0; i < len(xtop); i++ { n := xtop[i] - if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias { + if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias() { xtop[i] = typecheck(n, ctxStmt) } } @@ -1177,7 +1176,6 @@ func importfile(f *Val) *types.Pkg { } if path_ == "unsafe" { - imported_unsafe = true return unsafepkg } diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index 85e710086a..67d24ef0bc 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -11,6 +11,7 @@ import ( "runtime" "strconv" "strings" + "unicode" "unicode/utf8" "cmd/compile/internal/syntax" @@ -90,7 +91,11 @@ func (p *noder) makeSrcPosBase(b0 *syntax.PosBase) *src.PosBase { } else { // line directive base p0 := b0.Pos() - p1 := src.MakePos(p.makeSrcPosBase(p0.Base()), p0.Line(), p0.Col()) + p0b := p0.Base() + if p0b == b0 { + panic("infinite recursion in makeSrcPosBase") + } + p1 := src.MakePos(p.makeSrcPosBase(p0b), p0.Line(), p0.Col()) b1 = src.NewLinePragmaBase(p1, fn, fileh(fn), b0.Line(), b0.Col()) } p.basemap[b0] = b1 @@ -130,11 +135,13 @@ type noder struct { base *src.PosBase } - file *syntax.File - linknames []linkname - pragcgobuf [][]string - err chan syntax.Error - scope ScopeID + file *syntax.File + linknames []linkname + pragcgobuf [][]string + err chan syntax.Error + scope ScopeID + importedUnsafe bool + importedEmbed bool // scopeVars is a stack tracking the number of variables declared in the // current function at the moment each open scope was opened. @@ -236,7 +243,8 @@ type linkname struct { func (p *noder) node() { types.Block = 1 - imported_unsafe = false + p.importedUnsafe = false + p.importedEmbed = false p.setlineno(p.file.PkgName) mkpackage(p.file.PkgName.Value) @@ -249,7 +257,7 @@ func (p *noder) node() { xtop = append(xtop, p.decls(p.file.DeclList)...) for _, n := range p.linknames { - if !imported_unsafe { + if !p.importedUnsafe { p.yyerrorpos(n.pos, "//go:linkname only allowed in Go files that import \"unsafe\"") continue } @@ -324,7 +332,6 @@ func (p *noder) importDecl(imp *syntax.ImportDecl) { val := p.basicLit(imp.Path) ipkg := importfile(&val) - if ipkg == nil { if nerrors == 0 { Fatalf("phase error in import") @@ -332,6 +339,13 @@ func (p *noder) importDecl(imp *syntax.ImportDecl) { return } + if ipkg == unsafepkg { + p.importedUnsafe = true + } + if ipkg.Path == "embed" { + p.importedEmbed = true + } + ipkg.Direct = true var my *types.Sym @@ -373,6 +387,20 @@ func (p *noder) varDecl(decl *syntax.VarDecl) []*Node { } if pragma, ok := decl.Pragma.(*Pragma); ok { + if len(pragma.Embeds) > 0 { + if !p.importedEmbed { + // This check can't be done when building the list pragma.Embeds + // because that list is created before the noder starts walking over the file, + // so at that point it hasn't seen the imports. + // We're left to check now, just before applying the //go:embed lines. + for _, e := range pragma.Embeds { + p.yyerrorpos(e.Pos, "//go:embed only allowed in Go files that import \"embed\"") + } + } else { + exprs = varEmbed(p, names, typ, exprs, pragma.Embeds) + } + pragma.Embeds = nil + } p.checkUnused(pragma) } @@ -455,17 +483,17 @@ func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { param := n.Name.Param param.Ntype = typ - param.Alias = decl.Alias + param.SetAlias(decl.Alias) if pragma, ok := decl.Pragma.(*Pragma); ok { if !decl.Alias { - param.Pragma = pragma.Flag & TypePragmas + param.SetPragma(pragma.Flag & TypePragmas) pragma.Flag &^= TypePragmas } p.checkUnused(pragma) } nod := p.nod(decl, ODCLTYPE, n, nil) - if param.Alias && !langSupported(1, 9, localpkg) { + if param.Alias() && !langSupported(1, 9, localpkg) { yyerrorl(nod.Pos, "type aliases only supported as of -lang=go1.9") } return nod @@ -1493,13 +1521,15 @@ var allowedStdPragmas = map[string]bool{ "go:cgo_import_dynamic": true, "go:cgo_ldflag": true, "go:cgo_dynamic_linker": true, + "go:embed": true, "go:generate": true, } // *Pragma is the value stored in a syntax.Pragma during parsing. type Pragma struct { - Flag PragmaFlag // collected bits - Pos []PragmaPos // position of each individual flag + Flag PragmaFlag // collected bits + Pos []PragmaPos // position of each individual flag + Embeds []PragmaEmbed } type PragmaPos struct { @@ -1507,12 +1537,22 @@ type PragmaPos struct { Pos syntax.Pos } +type PragmaEmbed struct { + Pos syntax.Pos + Patterns []string +} + func (p *noder) checkUnused(pragma *Pragma) { for _, pos := range pragma.Pos { if pos.Flag&pragma.Flag != 0 { p.yyerrorpos(pos.Pos, "misplaced compiler directive") } } + if len(pragma.Embeds) > 0 { + for _, e := range pragma.Embeds { + p.yyerrorpos(e.Pos, "misplaced go:embed directive") + } + } } func (p *noder) checkUnusedDuringParse(pragma *Pragma) { @@ -1521,6 +1561,11 @@ func (p *noder) checkUnusedDuringParse(pragma *Pragma) { p.error(syntax.Error{Pos: pos.Pos, Msg: "misplaced compiler directive"}) } } + if len(pragma.Embeds) > 0 { + for _, e := range pragma.Embeds { + p.error(syntax.Error{Pos: e.Pos, Msg: "misplaced go:embed directive"}) + } + } } // pragma is called concurrently if files are parsed concurrently. @@ -1565,6 +1610,17 @@ func (p *noder) pragma(pos syntax.Pos, blankLine bool, text string, old syntax.P } p.linknames = append(p.linknames, linkname{pos, f[1], target}) + case text == "go:embed", strings.HasPrefix(text, "go:embed "): + args, err := parseGoEmbed(text[len("go:embed"):]) + if err != nil { + p.error(syntax.Error{Pos: pos, Msg: err.Error()}) + } + if len(args) == 0 { + p.error(syntax.Error{Pos: pos, Msg: "usage: //go:embed pattern..."}) + break + } + pragma.Embeds = append(pragma.Embeds, PragmaEmbed{pos, args}) + case strings.HasPrefix(text, "go:cgo_import_dynamic "): // This is permitted for general use because Solaris // code relies on it in golang.org/x/sys/unix and others. @@ -1637,3 +1693,64 @@ func mkname(sym *types.Sym) *Node { } return n } + +// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns. +// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings. +// go/build/read.go also processes these strings and contains similar logic. +func parseGoEmbed(args string) ([]string, error) { + var list []string + for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) { + var path string + Switch: + switch args[0] { + default: + i := len(args) + for j, c := range args { + if unicode.IsSpace(c) { + i = j + break + } + } + path = args[:i] + args = args[i:] + + case '`': + i := strings.Index(args[1:], "`") + if i < 0 { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + path = args[1 : 1+i] + args = args[1+i+1:] + + case '"': + i := 1 + for ; i < len(args); i++ { + if args[i] == '\\' { + i++ + continue + } + if args[i] == '"' { + q, err := strconv.Unquote(args[:i+1]) + if err != nil { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1]) + } + path = q + args = args[i+1:] + break Switch + } + } + if i >= len(args) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + + if args != "" { + r, _ := utf8.DecodeRuneInString(args) + if !unicode.IsSpace(r) { + return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args) + } + } + list = append(list, path) + } + return list, nil +} diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index f6557e2d15..226eb45252 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -14,6 +14,8 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" + "os" "sort" "strconv" ) @@ -125,6 +127,7 @@ func dumpdata() { itabsLen := len(itabs) dumpimportstrings() dumpbasictypes() + dumpembeds() // Calls to dumpsignats can generate functions, // like method wrappers and hash and equality routines. @@ -358,28 +361,31 @@ func dbvec(s *obj.LSym, off int, bv bvec) int { return off } +const ( + stringSymPrefix = "go.string." + stringSymPattern = ".gostring.%d.%x" +) + +// stringsym returns a symbol containing the string s. +// The symbol contains the string data, not a string header. func stringsym(pos src.XPos, s string) (data *obj.LSym) { var symname string if len(s) > 100 { // Huge strings are hashed to avoid long names in object files. // Indulge in some paranoia by writing the length of s, too, // as protection against length extension attacks. + // Same pattern is known to fileStringSym below. h := sha256.New() io.WriteString(h, s) - symname = fmt.Sprintf(".gostring.%d.%x", len(s), h.Sum(nil)) + symname = fmt.Sprintf(stringSymPattern, len(s), h.Sum(nil)) } else { // Small strings get named directly by their contents. symname = strconv.Quote(s) } - const prefix = "go.string." - symdataname := prefix + symname - - symdata := Ctxt.Lookup(symdataname) - + symdata := Ctxt.Lookup(stringSymPrefix + symname) if !symdata.OnList() { - // string data - off := dsname(symdata, 0, s, pos, "string") + off := dstringdata(symdata, 0, s, pos, "string") ggloblsym(symdata, int32(off), obj.DUPOK|obj.RODATA|obj.LOCAL) symdata.Set(obj.AttrContentAddressable, true) } @@ -387,26 +393,122 @@ func stringsym(pos src.XPos, s string) (data *obj.LSym) { return symdata } -var slicebytes_gen int +// fileStringSym returns a symbol for the contents and the size of file. +// If readonly is true, the symbol shares storage with any literal string +// or other file with the same content and is placed in a read-only section. +// If readonly is false, the symbol is a read-write copy separate from any other, +// for use as the backing store of a []byte. +// The content hash of file is copied into hash. (If hash is nil, nothing is copied.) +// The returned symbol contains the data itself, not a string header. +func fileStringSym(pos src.XPos, file string, readonly bool, hash []byte) (*obj.LSym, int64, error) { + f, err := os.Open(file) + if err != nil { + return nil, 0, err + } + defer f.Close() + info, err := f.Stat() + if err != nil { + return nil, 0, err + } + if !info.Mode().IsRegular() { + return nil, 0, fmt.Errorf("not a regular file") + } + size := info.Size() + if size <= 1*1024 { + data, err := ioutil.ReadAll(f) + if err != nil { + return nil, 0, err + } + if int64(len(data)) != size { + return nil, 0, fmt.Errorf("file changed between reads") + } + var sym *obj.LSym + if readonly { + sym = stringsym(pos, string(data)) + } else { + sym = slicedata(pos, string(data)).Sym.Linksym() + } + if len(hash) > 0 { + sum := sha256.Sum256(data) + copy(hash, sum[:]) + } + return sym, size, nil + } + if size > 2e9 { + // ggloblsym takes an int32, + // and probably the rest of the toolchain + // can't handle such big symbols either. + // See golang.org/issue/9862. + return nil, 0, fmt.Errorf("file too large") + } -func slicebytes(nam *Node, s string) { - slicebytes_gen++ - symname := fmt.Sprintf(".gobytes.%d", slicebytes_gen) + // File is too big to read and keep in memory. + // Compute hash if needed for read-only content hashing or if the caller wants it. + var sum []byte + if readonly || len(hash) > 0 { + h := sha256.New() + n, err := io.Copy(h, f) + if err != nil { + return nil, 0, err + } + if n != size { + return nil, 0, fmt.Errorf("file changed between reads") + } + sum = h.Sum(nil) + copy(hash, sum) + } + + var symdata *obj.LSym + if readonly { + symname := fmt.Sprintf(stringSymPattern, size, sum) + symdata = Ctxt.Lookup(stringSymPrefix + symname) + if !symdata.OnList() { + info := symdata.NewFileInfo() + info.Name = file + info.Size = size + ggloblsym(symdata, int32(size), obj.DUPOK|obj.RODATA|obj.LOCAL) + // Note: AttrContentAddressable cannot be set here, + // because the content-addressable-handling code + // does not know about file symbols. + } + } else { + // Emit a zero-length data symbol + // and then fix up length and content to use file. + symdata = slicedata(pos, "").Sym.Linksym() + symdata.Size = size + symdata.Type = objabi.SNOPTRDATA + info := symdata.NewFileInfo() + info.Name = file + info.Size = size + } + + return symdata, size, nil +} + +var slicedataGen int + +func slicedata(pos src.XPos, s string) *Node { + slicedataGen++ + symname := fmt.Sprintf(".gobytes.%d", slicedataGen) sym := localpkg.Lookup(symname) symnode := newname(sym) sym.Def = asTypesNode(symnode) lsym := sym.Linksym() - off := dsname(lsym, 0, s, nam.Pos, "slice") + off := dstringdata(lsym, 0, s, pos, "slice") ggloblsym(lsym, int32(off), obj.NOPTR|obj.LOCAL) + return symnode +} + +func slicebytes(nam *Node, s string) { if nam.Op != ONAME { Fatalf("slicebytes %v", nam) } - slicesym(nam, symnode, int64(len(s))) + slicesym(nam, slicedata(nam.Pos, s), int64(len(s))) } -func dsname(s *obj.LSym, off int, t string, pos src.XPos, what string) int { +func dstringdata(s *obj.LSym, off int, t string, pos src.XPos, what string) int { // Objects that are too large will cause the data section to overflow right away, // causing a cryptic error message by the linker. Check for oversize objects here // and provide a useful error message instead. diff --git a/src/cmd/compile/internal/gc/syntax.go b/src/cmd/compile/internal/gc/syntax.go index e3b4963977..83b5db834f 100644 --- a/src/cmd/compile/internal/gc/syntax.go +++ b/src/cmd/compile/internal/gc/syntax.go @@ -480,11 +480,87 @@ type Param struct { Innermost *Node Outer *Node - // OTYPE - // - // TODO: Should Func pragmas also be stored on the Name? - Pragma PragmaFlag - Alias bool // node is alias for Ntype (only used when type-checking ODCLTYPE) + // OTYPE & ONAME //go:embed info, + // sharing storage to reduce gc.Param size. + // Extra is nil, or else *Extra is a *paramType or an *embedFileList. + Extra *interface{} +} + +type paramType struct { + flag PragmaFlag + alias bool +} + +type embedFileList []string + +// Pragma returns the PragmaFlag for p, which must be for an OTYPE. +func (p *Param) Pragma() PragmaFlag { + if p.Extra == nil { + return 0 + } + return (*p.Extra).(*paramType).flag +} + +// SetPragma sets the PragmaFlag for p, which must be for an OTYPE. +func (p *Param) SetPragma(flag PragmaFlag) { + if p.Extra == nil { + if flag == 0 { + return + } + p.Extra = new(interface{}) + *p.Extra = ¶mType{flag: flag} + return + } + (*p.Extra).(*paramType).flag = flag +} + +// Alias reports whether p, which must be for an OTYPE, is a type alias. +func (p *Param) Alias() bool { + if p.Extra == nil { + return false + } + t, ok := (*p.Extra).(*paramType) + if !ok { + return false + } + return t.alias +} + +// SetAlias sets whether p, which must be for an OTYPE, is a type alias. +func (p *Param) SetAlias(alias bool) { + if p.Extra == nil { + if !alias { + return + } + p.Extra = new(interface{}) + *p.Extra = ¶mType{alias: alias} + return + } + (*p.Extra).(*paramType).alias = alias +} + +// EmbedFiles returns the list of embedded files for p, +// which must be for an ONAME var. +func (p *Param) EmbedFiles() []string { + if p.Extra == nil { + return nil + } + return *(*p.Extra).(*embedFileList) +} + +// SetEmbedFiles sets the list of embedded files for p, +// which must be for an ONAME var. +func (p *Param) SetEmbedFiles(list []string) { + if p.Extra == nil { + if len(list) == 0 { + return + } + f := embedFileList(list) + p.Extra = new(interface{}) + *p.Extra = &f + return + } + *(*p.Extra).(*embedFileList) = list } // Functions diff --git a/src/cmd/compile/internal/gc/typecheck.go b/src/cmd/compile/internal/gc/typecheck.go index a4b462da1d..ce817db446 100644 --- a/src/cmd/compile/internal/gc/typecheck.go +++ b/src/cmd/compile/internal/gc/typecheck.go @@ -257,12 +257,12 @@ func typecheck(n *Node, top int) (res *Node) { // are substituted. cycle := cycleFor(n) for _, n1 := range cycle { - if n1.Name != nil && !n1.Name.Param.Alias { + if n1.Name != nil && !n1.Name.Param.Alias() { // Cycle is ok. But if n is an alias type and doesn't // have a type yet, we have a recursive type declaration // with aliases that we can't handle properly yet. // Report an error rather than crashing later. - if n.Name != nil && n.Name.Param.Alias && n.Type == nil { + if n.Name != nil && n.Name.Param.Alias() && n.Type == nil { lineno = n.Pos Fatalf("cannot handle alias type declaration (issue #25838): %v", n) } @@ -3504,7 +3504,7 @@ func setUnderlying(t, underlying *types.Type) { } // Propagate go:notinheap pragma from the Name to the Type. - if n.Name != nil && n.Name.Param != nil && n.Name.Param.Pragma&NotInHeap != 0 { + if n.Name != nil && n.Name.Param != nil && n.Name.Param.Pragma()&NotInHeap != 0 { t.SetNotInHeap(true) } @@ -3676,7 +3676,7 @@ func typecheckdef(n *Node) { n.Name.Defn = typecheck(n.Name.Defn, ctxStmt) // fills in n.Type case OTYPE: - if p := n.Name.Param; p.Alias { + if p := n.Name.Param; p.Alias() { // Type alias declaration: Simply use the rhs type - no need // to create a new type. // If we have a syntax error, p.Ntype may be nil. diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index ad4708138f..2037beca72 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -401,7 +401,7 @@ type LSym struct { P []byte R []Reloc - Extra *interface{} // *FuncInfo if present + Extra *interface{} // *FuncInfo or *FileInfo, if present Pkg string PkgIdx int32 @@ -454,6 +454,33 @@ func (s *LSym) Func() *FuncInfo { return f } +// A FileInfo contains extra fields for SDATA symbols backed by files. +// (If LSym.Extra is a *FileInfo, LSym.P == nil.) +type FileInfo struct { + Name string // name of file to read into object file + Size int64 // length of file +} + +// NewFileInfo allocates and returns a FileInfo for LSym. +func (s *LSym) NewFileInfo() *FileInfo { + if s.Extra != nil { + log.Fatalf("invalid use of LSym - NewFileInfo with Extra of type %T", *s.Extra) + } + f := new(FileInfo) + s.Extra = new(interface{}) + *s.Extra = f + return f +} + +// File returns the *FileInfo associated with s, or else nil. +func (s *LSym) File() *FileInfo { + if s.Extra == nil { + return nil + } + f, _ := (*s.Extra).(*FileInfo) + return f +} + type InlMark struct { // When unwinding from an instruction in an inlined body, mark // where we should unwind to. diff --git a/src/cmd/internal/obj/objfile.go b/src/cmd/internal/obj/objfile.go index a24a7b878f..bb58b4f0c2 100644 --- a/src/cmd/internal/obj/objfile.go +++ b/src/cmd/internal/obj/objfile.go @@ -16,6 +16,8 @@ import ( "encoding/binary" "fmt" "io" + "log" + "os" "path/filepath" "sort" "strings" @@ -147,14 +149,20 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) { // Data indexes h.Offsets[goobj.BlkDataIdx] = w.Offset() - dataOff := uint32(0) + dataOff := int64(0) for _, list := range lists { for _, s := range list { - w.Uint32(dataOff) - dataOff += uint32(len(s.P)) + w.Uint32(uint32(dataOff)) + dataOff += int64(len(s.P)) + if file := s.File(); file != nil { + dataOff += int64(file.Size) + } } } - w.Uint32(dataOff) + if int64(uint32(dataOff)) != dataOff { + log.Fatalf("data too large") + } + w.Uint32(uint32(dataOff)) // Relocs h.Offsets[goobj.BlkReloc] = w.Offset() @@ -179,6 +187,9 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) { for _, list := range lists { for _, s := range list { w.Bytes(s.P) + if file := s.File(); file != nil { + w.writeFile(ctxt, file) + } } } @@ -218,6 +229,7 @@ func WriteObjFile(ctxt *Link, b *bio.Writer) { type writer struct { *goobj.Writer + filebuf []byte ctxt *Link pkgpath string // the package import path (escaped), "" if unknown pkglist []string // list of packages referenced, indexed by ctxt.pkgIdx @@ -232,6 +244,35 @@ func (w *writer) init() { } } +func (w *writer) writeFile(ctxt *Link, file *FileInfo) { + f, err := os.Open(file.Name) + if err != nil { + ctxt.Diag("%v", err) + return + } + defer f.Close() + if w.filebuf == nil { + w.filebuf = make([]byte, 1024) + } + buf := w.filebuf + written := int64(0) + for { + n, err := f.Read(buf) + w.Bytes(buf[:n]) + written += int64(n) + if err == io.EOF { + break + } + if err != nil { + ctxt.Diag("%v", err) + return + } + } + if written != file.Size { + ctxt.Diag("copy %s: unexpected length %d != %d", file.Name, written, file.Size) + } +} + func (w *writer) StringTable() { w.AddString("") for _, p := range w.ctxt.Imports { -- cgit v1.3