Commit c06a5335 authored by Alan Donovan's avatar Alan Donovan

exp/ssa: (#4 of 5): the SSA builder.

R=iant, gri, iant, rogpeppe
CC=golang-dev
https://golang.org/cl/7196053
parent 399dcc75
This diff is collapsed.
package ssa
// Helpers for emitting SSA instructions.
import (
"go/token"
"go/types"
)
// emitNew emits to f a new (heap Alloc) instruction allocating an
// object of type typ.
//
func emitNew(f *Function, typ types.Type) Value {
return f.emit(&Alloc{
Type_: pointer(typ),
Heap: true,
})
}
// emitLoad emits to f an instruction to load the address addr into a
// new temporary, and returns the value so defined.
//
func emitLoad(f *Function, addr Value) Value {
v := &UnOp{Op: token.MUL, X: addr}
v.setType(indirectType(addr.Type()))
return f.emit(v)
}
// emitArith emits to f code to compute the binary operation op(x, y)
// where op is an eager shift, logical or arithmetic operation.
// (Use emitCompare() for comparisons and Builder.logicalBinop() for
// non-eager operations.)
//
func emitArith(f *Function, op token.Token, x, y Value, t types.Type) Value {
switch op {
case token.SHL, token.SHR:
// TODO(adonovan): fix: is this correct?
x = emitConv(f, x, t)
y = emitConv(f, y, types.Typ[types.Uint64])
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
x = emitConv(f, x, t)
y = emitConv(f, y, t)
default:
panic("illegal op in emitArith: " + op.String())
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(t)
return f.emit(v)
}
// emitCompare emits to f code compute the boolean result of
// comparison comparison 'x op y'.
//
func emitCompare(f *Function, op token.Token, x, y Value) Value {
// TODO(adonovan): fix: this is incomplete.
xt := underlyingType(x.Type())
yt := underlyingType(y.Type())
// Special case to optimise a tagless SwitchStmt so that
// these are equivalent
// switch { case e: ...}
// switch true { case e: ... }
// if e==true { ... }
// even in the case when e's type is an interface.
// TODO(adonovan): generalise to x==true, false!=y, etc.
if x == vTrue && op == token.EQL {
if yt, ok := yt.(*types.Basic); ok && yt.Info&types.IsBoolean != 0 {
return y
}
}
if types.IsIdentical(xt, yt) {
// no conversion necessary
} else if _, ok := xt.(*types.Interface); ok {
y = emitConv(f, y, x.Type())
} else if _, ok := yt.(*types.Interface); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := x.(*Literal); ok {
x = emitConv(f, x, y.Type())
} else if _, ok := y.(*Literal); ok {
y = emitConv(f, y, x.Type())
} else {
// other cases, e.g. channels. No-op.
}
v := &BinOp{
Op: op,
X: x,
Y: y,
}
v.setType(tBool)
return f.emit(v)
}
// emitConv emits to f code to convert Value val to exactly type typ,
// and returns the converted value. Implicit conversions are implied
// by language assignability rules in the following operations:
//
// - from rvalue type to lvalue type in assignments.
// - from actual- to formal-parameter types in function calls.
// - from return value type to result type in return statements.
// - population of struct fields, array and slice elements, and map
// keys and values within compoisite literals
// - from index value to index type in indexing expressions.
// - for both arguments of comparisons.
// - from value type to channel type in send expressions.
//
func emitConv(f *Function, val Value, typ types.Type) Value {
// fmt.Printf("emitConv %s -> %s, %T", val.Type(), typ, val) // debugging
// Identical types? Conversion is a no-op.
if types.IsIdentical(val.Type(), typ) {
return val
}
ut_dst := underlyingType(typ)
ut_src := underlyingType(val.Type())
// Identical underlying types? Conversion is a name change.
if types.IsIdentical(ut_dst, ut_src) {
// TODO(adonovan): make this use a distinct
// instruction, ChangeType. This instruction must
// also cover the cases of channel type restrictions and
// conversions between pointers to identical base
// types.
c := &Conv{X: val}
c.setType(typ)
return f.emit(c)
}
// Conversion to, or construction of a value of, an interface type?
if _, ok := ut_dst.(*types.Interface); ok {
// Assignment from one interface type to a different one?
if _, ok := ut_src.(*types.Interface); ok {
c := &ChangeInterface{X: val}
c.setType(typ)
return f.emit(c)
}
// Untyped nil literal? Return interface-typed nil literal.
if ut_src == tUntypedNil {
return nilLiteral(typ)
}
// Convert (non-nil) "untyped" literals to their default type.
// TODO(gri): expose types.isUntyped().
if t, ok := ut_src.(*types.Basic); ok && t.Info&types.IsUntyped != 0 {
val = emitConv(f, val, DefaultType(ut_src))
}
mi := &MakeInterface{
X: val,
Methods: f.Prog.MethodSet(val.Type()),
}
mi.setType(typ)
return f.emit(mi)
}
// Conversion of a literal to a non-interface type results in
// a new literal of the destination type and (initially) the
// same abstract value. We don't compute the representation
// change yet; this defers the point at which the number of
// possible representations explodes.
if l, ok := val.(*Literal); ok {
return newLiteral(l.Value, typ)
}
// A representation-changing conversion.
c := &Conv{X: val}
c.setType(typ)
return f.emit(c)
}
// emitStore emits to f an instruction to store value val at location
// addr, applying implicit conversions as required by assignabilty rules.
//
func emitStore(f *Function, addr, val Value) {
f.emit(&Store{
Addr: addr,
Val: emitConv(f, val, indirectType(addr.Type())),
})
}
// emitJump emits to f a jump to target, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitJump(f *Function, target *BasicBlock) {
b := f.currentBlock
b.emit(new(Jump))
addEdge(b, target)
f.currentBlock = nil
}
// emitIf emits to f a conditional jump to tblock or fblock based on
// cond, and updates the control-flow graph.
// Postcondition: f.currentBlock is nil.
//
func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) {
b := f.currentBlock
b.emit(&If{Cond: cond})
addEdge(b, tblock)
addEdge(b, fblock)
f.currentBlock = nil
}
// emitExtract emits to f an instruction to extract the index'th
// component of tuple, ascribing it type typ. It returns the
// extracted value.
//
func emitExtract(f *Function, tuple Value, index int, typ types.Type) Value {
e := &Extract{Tuple: tuple, Index: index}
// In all cases but one (tSelect's recv), typ is redundant w.r.t.
// tuple.Type().(*types.Result).Values[index].Type.
e.setType(typ)
return f.emit(e)
}
// emitTailCall emits to f a function call in tail position.
// Postcondition: f.currentBlock is nil.
//
func emitTailCall(f *Function, call *Call) {
tuple := f.emit(call)
var ret Ret
switch {
case len(f.Signature.Results) > 1:
for i, o := range call.Type().(*types.Result).Values {
v := emitExtract(f, tuple, i, o.Type)
// TODO(adonovan): in principle, this is required:
// v = emitConv(f, o.Type, f.Signature.Results[i].Type)
// but in practice emitTailCall is only used when
// the types exactly match.
ret.Results = append(ret.Results, v)
}
case len(f.Signature.Results) == 1:
ret.Results = []Value{tuple}
default:
// no-op
}
f.emit(&ret)
f.currentBlock = nil
}
// emitSelfLoop emits to f a self-loop.
// This is a defensive measure to ensure control-flow integrity.
// It should never be reachable.
// Postcondition: f.currentBlock is nil.
//
func emitSelfLoop(f *Function) {
loop := f.newBasicBlock("selfloop")
emitJump(f, loop)
f.currentBlock = loop
emitJump(f, loop)
}
......@@ -10,18 +10,6 @@ import (
"os"
)
// Mode bits for additional diagnostics and checking.
// TODO(adonovan): move these to builder.go once submitted.
type BuilderMode uint
const (
LogPackages BuilderMode = 1 << iota // Dump package inventory to stderr
LogFunctions // Dump function SSA code to stderr
LogSource // Show source locations as SSA builder progresses
SanityCheckFunctions // Perform sanity checking of function bodies
UseGCImporter // Ignore SourceLoader; use gc-compiled object code for all imports
)
// addEdge adds a control-flow graph edge from from to to.
func addEdge(from, to *BasicBlock) {
from.Succs = append(from.Succs, to)
......@@ -182,8 +170,8 @@ func (f *Function) addSpilledParam(obj types.Object) {
// Otherwise, idents is ignored and the usual set-up for Go source
// functions is skipped.
//
func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if mode&LogSource != 0 {
func (f *Function) start(idents map[*ast.Ident]types.Object) {
if f.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s @ %s\n", f.FullName(), f.Prog.Files.Position(f.Pos))
}
f.currentBlock = f.newBasicBlock("entry")
......@@ -226,7 +214,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
}
// finish() finalizes the function after SSA code generation of its body.
func (f *Function) finish(mode BuilderMode) {
func (f *Function) finish() {
f.objects = nil
f.results = nil
f.currentBlock = nil
......@@ -269,13 +257,13 @@ func (f *Function) finish(mode BuilderMode) {
}
optimizeBlocks(f)
if mode&LogFunctions != 0 {
if f.Prog.mode&LogFunctions != 0 {
f.DumpTo(os.Stderr)
}
if mode&SanityCheckFunctions != 0 {
if f.Prog.mode&SanityCheckFunctions != 0 {
MustSanityCheck(f, nil)
}
if mode&LogSource != 0 {
if f.Prog.mode&LogSource != 0 {
fmt.Fprintf(os.Stderr, "build function %s done\n", f.FullName())
}
}
......@@ -345,6 +333,46 @@ func (f *Function) emit(instr Instruction) Value {
return f.currentBlock.emit(instr)
}
// FullName returns the full name of this function, qualified by
// package name, receiver type, etc.
//
// Examples:
// "math.IsNaN" // a package-level function
// "(*sync.WaitGroup).Add" // a declared method
// "(*exp/ssa.Ret).Block" // a bridge method
// "(ssa.Instruction).Block" // an interface method thunk
// "func@5.32" // an anonymous function
//
func (f *Function) FullName() string {
// Anonymous?
if f.Enclosing != nil {
return f.Name_
}
recv := f.Signature.Recv
// Synthetic?
if f.Pkg == nil {
if recv != nil {
// TODO(adonovan): print type package-qualified, if NamedType.
return fmt.Sprintf("(%s).%s", recv.Type, f.Name_) // bridge method
}
return fmt.Sprintf("(%s).%s", f.Params[0].Type(), f.Name_) // interface method thunk
}
// Declared method?
if recv != nil {
star := ""
if isPointer(recv.Type) {
star = "*"
}
return fmt.Sprintf("(%s%s.%s).%s", star, f.Pkg.ImportPath, deref(recv.Type), f.Name_)
}
// Package-level function.
return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
}
// DumpTo prints to w a human readable "disassembly" of the SSA code of
// all basic blocks of function f.
//
......@@ -364,18 +392,21 @@ func (f *Function) DumpTo(w io.Writer) {
}
}
io.WriteString(w, "func ")
params := f.Params
if f.Signature.Recv != nil {
fmt.Fprintf(w, "func (%s) %s(", params[0].Name(), f.Name())
fmt.Fprintf(w, "(%s %s) ", params[0].Name(), params[0].Type())
params = params[1:]
} else {
fmt.Fprintf(w, "func %s(", f.Name())
}
io.WriteString(w, f.Name())
io.WriteString(w, "(")
for i, v := range params {
if i > 0 {
io.WriteString(w, ", ")
}
io.WriteString(w, v.Name())
io.WriteString(w, " ")
io.WriteString(w, v.Type().String())
}
io.WriteString(w, "):\n")
......
package ssa
// This file defines an implementation of the types.Importer interface
// (func) that loads the transitive closure of dependencies of a
// "main" package.
import (
"go/ast"
"go/build"
"go/parser"
"go/token"
"go/types"
"os"
"path/filepath"
)
// Prototype of a function that locates, reads and parses a set of
// source files given an import path.
//
// fset is the fileset to which the ASTs should be added.
// path is the imported path, e.g. "sync/atomic".
//
// On success, the function returns files, the set of ASTs produced,
// or the first error encountered.
//
type SourceLoader func(fset *token.FileSet, path string) (files []*ast.File, err error)
// doImport loads the typechecker package identified by path
// Implements the types.Importer prototype.
//
func (b *Builder) doImport(imports map[string]*types.Package, path string) (typkg *types.Package, err error) {
// Package unsafe is handled specially, and has no ssa.Package.
if path == "unsafe" {
return types.Unsafe, nil
}
if pkg := b.Prog.Packages[path]; pkg != nil {
typkg = pkg.Types
imports[path] = typkg
return // positive cache hit
}
if err = b.importErrs[path]; err != nil {
return // negative cache hit
}
var files []*ast.File
if b.mode&UseGCImporter != 0 {
typkg, err = types.GcImport(imports, path)
} else {
files, err = b.loader(b.Prog.Files, path)
if err == nil {
typkg, err = b.typechecker.Check(b.Prog.Files, files)
}
}
if err != nil {
// Cache failure
b.importErrs[path] = err
return nil, err
}
// Cache success
imports[path] = typkg // cache for just this package.
b.Prog.Packages[path] = b.createPackageImpl(typkg, path, files) // cache across all packages
return typkg, nil
}
// GorootLoader is an implementation of the SourceLoader function
// prototype that loads and parses Go source files from the package
// directory beneath $GOROOT/src/pkg.
//
// TODO(adonovan): get rsc and adg (go/build owners) to review this.
//
func GorootLoader(fset *token.FileSet, path string) (files []*ast.File, err error) {
// TODO(adonovan): fix: Do we need cwd? Shouldn't ImportDir(path) / $GOROOT suffice?
srcDir, err := os.Getwd()
if err != nil {
return // serious misconfiguration
}
bp, err := build.Import(path, srcDir, 0)
if err != nil {
return // import failed
}
files, err = ParseFiles(fset, bp.Dir, bp.GoFiles...)
if err != nil {
return nil, err
}
return
}
// ParseFiles parses the Go source files files within directory dir
// and returns their ASTs, or the first parse error if any.
//
// This utility function is provided to facilitate implementing a
// SourceLoader.
//
func ParseFiles(fset *token.FileSet, dir string, files ...string) (parsed []*ast.File, err error) {
for _, file := range files {
var f *ast.File
if !filepath.IsAbs(file) {
file = filepath.Join(dir, file)
}
f, err = parser.ParseFile(fset, file, nil, parser.DeclarationErrors)
if err != nil {
return // parsing failed
}
parsed = append(parsed, f)
}
return
}
package ssa
// lvalues are the union of addressable expressions and map-index
// expressions.
import (
"go/types"
)
// An lvalue represents an assignable location that may appear on the
// left-hand side of an assignment. This is a generalization of a
// pointer to permit updates to elements of maps.
//
type lvalue interface {
store(fn *Function, v Value) // stores v into the location
load(fn *Function) Value // loads the contents of the location
typ() types.Type // returns the type of the location
}
// An address is an lvalue represented by a true pointer.
type address struct {
addr Value
}
func (a address) load(fn *Function) Value {
return emitLoad(fn, a.addr)
}
func (a address) store(fn *Function, v Value) {
emitStore(fn, a.addr, v)
}
func (a address) typ() types.Type {
return indirectType(a.addr.Type())
}
// An element is an lvalue represented by m[k], the location of an
// element of a map or string. These locations are not addressable
// since pointers cannot be formed from them, but they do support
// load(), and in the case of maps, store().
//
type element struct {
m, k Value // map or string
t types.Type // map element type or string byte type
}
func (e *element) load(fn *Function) Value {
l := &Lookup{
X: e.m,
Index: e.k,
}
l.setType(e.t)
return fn.emit(l)
}
func (e *element) store(fn *Function, v Value) {
fn.emit(&MapUpdate{
Map: e.m,
Key: e.k,
Value: emitConv(fn, v, e.t),
})
}
func (e *element) typ() types.Type {
return e.t
}
// A blanks is a dummy variable whose name is "_".
// It is not reified: loads are illegal and stores are ignored.
//
type blank struct{}
func (bl blank) load(fn *Function) Value {
panic("blank.load is illegal")
}
func (bl blank) store(fn *Function, v Value) {
// no-op
}
func (bl blank) typ() types.Type {
// TODO(adonovan): this should be the type of the blank Ident;
// the typechecker doesn't provide this yet, but fortunately,
// we don't need it yet either.
panic("blank.typ is unimplemented")
}
......@@ -8,6 +8,8 @@ import (
"fmt"
"go/ast"
"go/types"
"io"
"sort"
)
func (id Id) String() string {
......@@ -67,18 +69,6 @@ func (r *Function) String() string {
return fmt.Sprintf("function %s : %s", r.Name(), r.Type())
}
// FullName returns the name of this function qualified by the
// package name, unless it is anonymous or synthetic.
//
// TODO(adonovan): move to func.go when it's submitted.
//
func (f *Function) FullName() string {
if f.Enclosing != nil || f.Pkg == nil {
return f.Name_ // anonymous or synthetic
}
return fmt.Sprintf("%s.%s", f.Pkg.ImportPath, f.Name_)
}
// FullName returns g's package-qualified name.
func (g *Global) FullName() string {
return fmt.Sprintf("%s.%s", g.Pkg.ImportPath, g.Name_)
......@@ -340,39 +330,51 @@ func (s *MapUpdate) String() string {
}
func (p *Package) String() string {
// TODO(adonovan): prettify output.
var b bytes.Buffer
fmt.Fprintf(&b, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
return "Package " + p.ImportPath
}
func (p *Package) DumpTo(w io.Writer) {
fmt.Fprintf(w, "Package %s at %s:\n", p.ImportPath, p.Prog.Files.File(p.Pos).Name())
// TODO(adonovan): make order deterministic.
var names []string
maxname := 0
for name := range p.Members {
if l := len(name); l > maxname {
maxname = l
}
names = append(names, name)
}
for name, mem := range p.Members {
switch mem := mem.(type) {
sort.Strings(names)
for _, name := range names {
switch mem := p.Members[name].(type) {
case *Literal:
fmt.Fprintf(&b, " const %-*s %s\n", maxname, name, mem.Name())
fmt.Fprintf(w, " const %-*s %s\n", maxname, name, mem.Name())
case *Function:
fmt.Fprintf(&b, " func %-*s %s\n", maxname, name, mem.Type())
fmt.Fprintf(w, " func %-*s %s\n", maxname, name, mem.Type())
case *Type:
fmt.Fprintf(&b, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// TODO(adonovan): make order deterministic.
for name, method := range mem.Methods {
fmt.Fprintf(&b, " method %s %s\n", name, method.Signature)
fmt.Fprintf(w, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying)
// We display only PtrMethods since its keys
// are a superset of Methods' keys, though the
// methods themselves may differ,
// e.g. different bridge methods.
var keys ids
for id := range mem.PtrMethods {
keys = append(keys, id)
}
sort.Sort(keys)
for _, id := range keys {
method := mem.PtrMethods[id]
fmt.Fprintf(w, " method %s %s\n", id, method.Signature)
}
case *Global:
fmt.Fprintf(&b, " var %-*s %s\n", maxname, name, mem.Type())
fmt.Fprintf(w, " var %-*s %s\n", maxname, name, mem.Type())
}
}
return b.String()
}
func commaOk(x bool) string {
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ import (
"go/ast"
"go/token"
"go/types"
"sync"
)
// A Program is a partial or complete Go program converted to SSA form.
......@@ -16,18 +17,17 @@ import (
//
// TODO(adonovan): synthetic methods for promoted methods and for
// standalone interface methods do not belong to any package. Make
// them enumerable here.
//
// TODO(adonovan): MethodSets of types other than named types
// (i.e. anon structs) are not currently accessible, nor are they
// memoized. Add a method: MethodSetForType() which looks in the
// appropriate Package (for methods of named types) or in
// Program.AnonStructMethods (for methods of anon structs).
// them enumerable here so clients can (e.g.) generate code for them.
//
type Program struct {
Files *token.FileSet // position information for the files of this Program
Packages map[string]*Package // all loaded Packages, keyed by import path
Builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup]
methodSetsMu sync.Mutex // serializes all accesses to methodSets
concreteMethods map[*types.Method]*Function // maps named concrete methods to their code
mode BuilderMode // set of mode bits
}
// A Package is a single analyzed Go package, containing Members for
......@@ -80,27 +80,18 @@ type Id struct {
Name string
}
// A MethodSet contains all the methods whose receiver is either T or
// *T, for some named or struct type T.
//
// TODO(adonovan): the client is required to adapt T<=>*T, e.g. when
// invoking an interface method. (This could be simplified for the
// client by having distinct method sets for T and *T, with the SSA
// Builder generating wrappers as needed, but probably the client is
// able to do a better job.) Document the precise rules the client
// must follow.
// A MethodSet contains all the methods for a particular type.
// The method sets for T and *T are distinct entities.
//
type MethodSet map[Id]*Function
// A Type is a Member of a Package representing the name, underlying
// type and method set of a named type declared at package scope.
//
// The method set contains only concrete methods; it is empty for
// interface types.
//
type Type struct {
NamedType *types.NamedType
Methods MethodSet
NamedType *types.NamedType
Methods MethodSet // concrete method set of N
PtrMethods MethodSet // concrete method set of (*N)
}
// An SSA value that can be referenced by an instruction.
......@@ -479,7 +470,7 @@ type ChangeInterface struct {
type MakeInterface struct {
Register
X Value
Methods MethodSet // method set of (non-interface) X iff converting to interface
Methods MethodSet // method set of (non-interface) X
}
// A MakeClosure instruction yields an anonymous function value whose
......
......@@ -6,6 +6,7 @@ import (
"fmt"
"go/ast"
"go/types"
"reflect"
)
func unreachable() {
......@@ -118,6 +119,26 @@ func objKind(obj types.Object) ast.ObjKind {
panic(fmt.Sprintf("unexpected Object type: %T", obj))
}
// canHaveConcreteMethods returns true iff typ may have concrete
// methods associated with it. Callers must supply allowPtr=true.
//
// TODO(gri): consider putting this in go/types. It's surprisingly subtle.
func canHaveConcreteMethods(typ types.Type, allowPtr bool) bool {
switch typ := typ.(type) {
case *types.Pointer:
return allowPtr && canHaveConcreteMethods(typ.Base, false)
case *types.NamedType:
switch typ.Underlying.(type) {
case *types.Pointer, *types.Interface:
return false
}
return true
case *types.Struct:
return true
}
return false
}
// DefaultType returns the default "typed" type for an "untyped" type;
// it returns the incoming type for all other types. If there is no
// corresponding untyped type, the result is types.Typ[types.Invalid].
......@@ -158,6 +179,10 @@ func makeId(name string, pkg *types.Package) (id Id) {
id.Name = name
if !ast.IsExported(name) {
id.Pkg = pkg
// TODO(gri): fix
// if pkg.Path == "" {
// panic("Package " + pkg.Name + "has empty Path")
// }
}
return
}
......@@ -170,3 +195,16 @@ func makeId(name string, pkg *types.Package) (id Id) {
func IdFromQualifiedName(qn types.QualifiedName) Id {
return makeId(qn.Name, qn.Pkg)
}
type ids []Id // a sortable slice of Id
func (p ids) Len() int { return len(p) }
func (p ids) Less(i, j int) bool {
x, y := p[i], p[j]
// *Package pointers are canonical so order by them.
// Don't use x.Pkg.ImportPath because sometimes it's empty.
// (TODO(gri): fix that.)
return reflect.ValueOf(x.Pkg).Pointer() < reflect.ValueOf(y.Pkg).Pointer() ||
x.Pkg == y.Pkg && x.Name < y.Name
}
func (p ids) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment