Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
R
riscv-gcc-1
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lvzhengyang
riscv-gcc-1
Commits
777133fe
Commit
777133fe
authored
Jan 30, 2013
by
Ian Lance Taylor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
libgo: Update Go library to master revision 15502/229081515358.
From-SVN: r195569
parent
900f0840
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
158 additions
and
98 deletions
+158
-98
libgo/MERGE
+1
-1
libgo/go/encoding/json/bench_test.go
+33
-1
libgo/go/encoding/json/decode.go
+0
-19
libgo/go/encoding/json/decode_test.go
+22
-1
libgo/go/encoding/json/stream.go
+7
-0
libgo/go/encoding/json/stream_test.go
+24
-0
libgo/go/exp/ssa/blockopt.go
+1
-5
libgo/go/exp/ssa/func.go
+31
-19
libgo/go/exp/ssa/ssa.go
+9
-21
libgo/go/go/build/build.go
+1
-7
libgo/go/go/build/build_test.go
+0
-12
libgo/runtime/go-traceback.c
+3
-3
libgo/runtime/panic.c
+5
-0
libgo/runtime/proc.c
+10
-4
libgo/runtime/runtime.c
+4
-2
libgo/runtime/runtime.h
+4
-2
libgo/runtime/time.goc
+3
-1
No files found.
libgo/MERGE
View file @
777133fe
921e53d4863c
229081515358
The first line of this file holds the Mercurial revision number of the
last merge done from the master library sources.
libgo/go/encoding/json/bench_test.go
View file @
777133fe
...
...
@@ -153,5 +153,37 @@ func BenchmarkCodeUnmarshalReuse(b *testing.B) {
b
.
Fatal
(
"Unmmarshal:"
,
err
)
}
}
b
.
SetBytes
(
int64
(
len
(
codeJSON
)))
}
func
BenchmarkUnmarshalString
(
b
*
testing
.
B
)
{
data
:=
[]
byte
(
`"hello, world"`
)
var
s
string
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
if
err
:=
Unmarshal
(
data
,
&
s
);
err
!=
nil
{
b
.
Fatal
(
"Unmarshal:"
,
err
)
}
}
}
func
BenchmarkUnmarshalFloat64
(
b
*
testing
.
B
)
{
var
f
float64
data
:=
[]
byte
(
`3.14`
)
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
if
err
:=
Unmarshal
(
data
,
&
f
);
err
!=
nil
{
b
.
Fatal
(
"Unmarshal:"
,
err
)
}
}
}
func
BenchmarkUnmarshalInt64
(
b
*
testing
.
B
)
{
var
x
int64
data
:=
[]
byte
(
`3`
)
for
i
:=
0
;
i
<
b
.
N
;
i
++
{
if
err
:=
Unmarshal
(
data
,
&
x
);
err
!=
nil
{
b
.
Fatal
(
"Unmarshal:"
,
err
)
}
}
}
libgo/go/encoding/json/decode.go
View file @
777133fe
...
...
@@ -52,25 +52,6 @@ import (
// an UnmarshalTypeError describing the earliest such error.
//
func
Unmarshal
(
data
[]
byte
,
v
interface
{})
error
{
// skip heavy processing for primitive values
var
first
byte
var
i
int
for
i
,
first
=
range
data
{
if
!
isSpace
(
rune
(
first
))
{
break
}
}
if
first
!=
'{'
&&
first
!=
'['
{
rv
:=
reflect
.
ValueOf
(
v
)
if
rv
.
Kind
()
!=
reflect
.
Ptr
||
rv
.
IsNil
()
{
return
&
InvalidUnmarshalError
{
reflect
.
TypeOf
(
v
)}
}
var
d
decodeState
d
.
literalStore
(
data
[
i
:
],
rv
.
Elem
(),
false
)
return
d
.
savedError
}
d
:=
new
(
decodeState
)
.
init
(
data
)
// Quick check for well-formedness.
...
...
libgo/go/encoding/json/decode_test.go
View file @
777133fe
...
...
@@ -1059,12 +1059,33 @@ func TestUnmarshalTypeError(t *testing.T) {
for
_
,
item
:=
range
decodeTypeErrorTests
{
err
:=
Unmarshal
([]
byte
(
item
.
src
),
item
.
dest
)
if
_
,
ok
:=
err
.
(
*
UnmarshalTypeError
);
!
ok
{
t
.
Errorf
(
"expected type error for Unmarshal(%q, type %T): got %
v instead
"
,
t
.
Errorf
(
"expected type error for Unmarshal(%q, type %T): got %
T
"
,
item
.
src
,
item
.
dest
,
err
)
}
}
}
var
unmarshalSyntaxTests
=
[]
string
{
"tru"
,
"fals"
,
"nul"
,
"123e"
,
`"hello`
,
`[1,2,3`
,
`{"key":1`
,
`{"key":1,`
,
}
func
TestUnmarshalSyntax
(
t
*
testing
.
T
)
{
var
x
interface
{}
for
_
,
src
:=
range
unmarshalSyntaxTests
{
err
:=
Unmarshal
([]
byte
(
src
),
&
x
)
if
_
,
ok
:=
err
.
(
*
SyntaxError
);
!
ok
{
t
.
Errorf
(
"expected syntax error for Unmarshal(%q): got %T"
,
src
,
err
)
}
}
}
// Test handling of unexported fields that should be ignored.
// Issue 4660
type
unexportedFields
struct
{
...
...
libgo/go/encoding/json/stream.go
View file @
777133fe
...
...
@@ -5,6 +5,7 @@
package
json
import
(
"bytes"
"errors"
"io"
)
...
...
@@ -58,6 +59,12 @@ func (dec *Decoder) Decode(v interface{}) error {
return
err
}
// Buffered returns a reader of the data remaining in the Decoder's
// buffer. The reader is valid until the next call to Decode.
func
(
dec
*
Decoder
)
Buffered
()
io
.
Reader
{
return
bytes
.
NewReader
(
dec
.
buf
)
}
// readValue reads a JSON value into dec.buf.
// It returns the length of the encoding.
func
(
dec
*
Decoder
)
readValue
()
(
int
,
error
)
{
...
...
libgo/go/encoding/json/stream_test.go
View file @
777133fe
...
...
@@ -6,8 +6,10 @@ package json
import
(
"bytes"
"io/ioutil"
"net"
"reflect"
"strings"
"testing"
)
...
...
@@ -83,6 +85,28 @@ func TestDecoder(t *testing.T) {
}
}
func
TestDecoderBuffered
(
t
*
testing
.
T
)
{
r
:=
strings
.
NewReader
(
`{"Name": "Gopher"} extra `
)
var
m
struct
{
Name
string
}
d
:=
NewDecoder
(
r
)
err
:=
d
.
Decode
(
&
m
)
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
m
.
Name
!=
"Gopher"
{
t
.
Errorf
(
"Name = %q; want Gopher"
,
m
.
Name
)
}
rest
,
err
:=
ioutil
.
ReadAll
(
d
.
Buffered
())
if
err
!=
nil
{
t
.
Fatal
(
err
)
}
if
g
,
w
:=
string
(
rest
),
" extra "
;
g
!=
w
{
t
.
Errorf
(
"Remaining = %q; want %q"
,
g
,
w
)
}
}
func
nlines
(
s
string
,
n
int
)
string
{
if
n
<=
0
{
return
""
...
...
libgo/go/exp/ssa/blockopt.go
View file @
777133fe
...
...
@@ -5,10 +5,6 @@ package ssa
// TODO(adonovan): instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage.
//
// TODO(adonovan): in the absence of multiway branch instructions,
// each BasicBlock has 0, 1, or 2 successors. We should preallocate
// the backing array for the Succs slice inline in BasicBlock.
import
(
"fmt"
...
...
@@ -117,7 +113,7 @@ func fuseBlocks(f *Function, a *BasicBlock) bool {
}
// A inherits B's successors
a
.
Succs
=
b
.
Succs
a
.
Succs
=
append
(
a
.
succs2
[
:
0
],
b
.
Succs
...
)
// Fix up Preds links of all successors of B.
for
_
,
c
:=
range
b
.
Succs
{
...
...
libgo/go/exp/ssa/func.go
View file @
777133fe
...
...
@@ -151,16 +151,27 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock {
func
(
f
*
Function
)
addParam
(
name
string
,
typ
types
.
Type
)
*
Parameter
{
v
:=
&
Parameter
{
Name_
:
name
,
Type_
:
pointer
(
typ
),
// address of param
Type_
:
typ
,
}
f
.
Params
=
append
(
f
.
Params
,
v
)
return
v
}
func
(
f
*
Function
)
addObjParam
(
obj
types
.
Object
)
*
Parameter
{
p
:=
f
.
addParam
(
obj
.
GetName
(),
obj
.
GetType
())
f
.
objects
[
obj
]
=
p
return
p
// addSpilledParam declares a parameter that is pre-spilled to the
// stack; the function body will load/store the spilled location.
// Subsequent registerization will eliminate spills where possible.
//
func
(
f
*
Function
)
addSpilledParam
(
obj
types
.
Object
)
{
name
:=
obj
.
GetName
()
param
:=
f
.
addParam
(
name
,
obj
.
GetType
())
spill
:=
&
Alloc
{
Name_
:
name
+
"~"
,
// "~" means "spilled"
Type_
:
pointer
(
obj
.
GetType
()),
}
f
.
objects
[
obj
]
=
spill
f
.
Locals
=
append
(
f
.
Locals
,
spill
)
f
.
emit
(
spill
)
f
.
emit
(
&
Store
{
Addr
:
spill
,
Val
:
param
})
}
// start initializes the function prior to generating SSA code for its body.
...
...
@@ -186,7 +197,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if
f
.
syntax
.
recvField
!=
nil
{
for
_
,
field
:=
range
f
.
syntax
.
recvField
.
List
{
for
_
,
n
:=
range
field
.
Names
{
f
.
add
Obj
Param
(
idents
[
n
])
f
.
add
Spilled
Param
(
idents
[
n
])
}
if
field
.
Names
==
nil
{
f
.
addParam
(
f
.
Signature
.
Recv
.
Name
,
f
.
Signature
.
Recv
.
Type
)
...
...
@@ -198,7 +209,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if
f
.
syntax
.
paramFields
!=
nil
{
for
_
,
field
:=
range
f
.
syntax
.
paramFields
.
List
{
for
_
,
n
:=
range
field
.
Names
{
f
.
add
Obj
Param
(
idents
[
n
])
f
.
add
Spilled
Param
(
idents
[
n
])
}
}
}
...
...
@@ -300,18 +311,18 @@ func (f *Function) addLocal(typ types.Type) *Alloc {
func
(
f
*
Function
)
lookup
(
obj
types
.
Object
,
escaping
bool
)
Value
{
if
v
,
ok
:=
f
.
objects
[
obj
];
ok
{
if
escaping
{
switch
v
:=
v
.
(
type
)
{
case
*
Capture
:
// TODO(adonovan): fix: we must support this case.
// Requires copying to a 'new' Alloc.
fmt
.
Fprintln
(
os
.
Stderr
,
"Error: escaping reference to Capture"
)
case
*
Parameter
:
v
.
Heap
=
true
case
*
Alloc
:
v
.
Heap
=
true
default
:
panic
(
fmt
.
Sprintf
(
"Unexpected Function.objects kind: %T"
,
v
))
// Walk up the chain of Captures.
x
:=
v
for
{
if
c
,
ok
:=
x
.
(
*
Capture
);
ok
{
x
=
c
.
Outer
}
else
{
break
}
}
// By construction, all captures are ultimately Allocs in the
// naive SSA form. Parameters are pre-spilled to the stack.
x
.
(
*
Alloc
)
.
Heap
=
true
}
return
v
// function-local var (address)
}
...
...
@@ -340,7 +351,7 @@ func (f *Function) emit(instr Instruction) Value {
func
(
f
*
Function
)
DumpTo
(
w
io
.
Writer
)
{
fmt
.
Fprintf
(
w
,
"# Name: %s
\n
"
,
f
.
FullName
())
fmt
.
Fprintf
(
w
,
"# Declared at %s
\n
"
,
f
.
Prog
.
Files
.
Position
(
f
.
Pos
))
fmt
.
Fprintf
(
w
,
"# Type: %s
\n
"
,
f
.
Type
()
)
fmt
.
Fprintf
(
w
,
"# Type: %s
\n
"
,
f
.
Signature
)
if
f
.
Enclosing
!=
nil
{
fmt
.
Fprintf
(
w
,
"# Parent: %s
\n
"
,
f
.
Enclosing
.
Name
())
...
...
@@ -411,6 +422,7 @@ func (f *Function) newBasicBlock(name string) *BasicBlock {
Name
:
fmt
.
Sprintf
(
"%d.%s"
,
len
(
f
.
Blocks
),
name
),
Func
:
f
,
}
b
.
Succs
=
b
.
succs2
[
:
0
]
f
.
Blocks
=
append
(
f
.
Blocks
,
b
)
return
b
}
libgo/go/exp/ssa/ssa.go
View file @
777133fe
...
...
@@ -246,19 +246,20 @@ type Function struct {
// instructions, respectively).
//
type
BasicBlock
struct
{
Name
string
// label; no semantic significance
Func
*
Function
// containing function
Instrs
[]
Instruction
// instructions in order
Preds
,
Succs
[]
*
BasicBlock
// predecessors and successors
Name
string
// label; no semantic significance
Func
*
Function
// containing function
Instrs
[]
Instruction
// instructions in order
Preds
,
Succs
[]
*
BasicBlock
// predecessors and successors
succs2
[
2
]
*
BasicBlock
// initial space for Succs.
}
// Pure values ----------------------------------------
// A Capture is a pointer to a lexically enclosing local variable.
//
// The referent of a capture is a
Parameter, Alloc or another Capture
// a
nd is always considered potentially escaping, so Captures are
// a
lways a
ddresses in the heap, and have pointer types.
// The referent of a capture is a
n Alloc or another Capture and is
// a
lways considered potentially escaping, so Captures are always
// addresses in the heap, and have pointer types.
//
type
Capture
struct
{
Outer
Value
// the Value captured from the enclosing context.
...
...
@@ -266,22 +267,9 @@ type Capture struct {
// A Parameter represents an input parameter of a function.
//
// Parameters are addresses and thus have pointer types.
// TODO(adonovan): this will change. We should just spill parameters
// to ordinary Alloc-style locals if they are ever used in an
// addressable context. Then we can lose the Heap flag.
//
// In the common case where Heap=false, Parameters are pointers into
// the function's stack frame. If the case where Heap=true because a
// parameter's address may escape from its function, Parameters are
// pointers into a space in the heap implicitly allocated during the
// function call. (See also Alloc, which uses the Heap flag in a
// similar manner.)
//
type
Parameter
struct
{
Name_
string
Type_
*
types
.
Pointer
Heap
bool
Type_
types
.
Type
}
// A Literal represents a literal nil, boolean, string or numeric
...
...
libgo/go/go/build/build.go
View file @
777133fe
...
...
@@ -321,13 +321,7 @@ func (p *Package) IsCommand() bool {
// ImportDir is like Import but processes the Go package found in
// the named directory.
func
(
ctxt
*
Context
)
ImportDir
(
dir
string
,
mode
ImportMode
)
(
*
Package
,
error
)
{
p
,
err
:=
ctxt
.
Import
(
"."
,
dir
,
mode
)
// TODO(rsc,adg): breaks godoc net/http. Not sure why.
// See CL 7232047 and issue 4696.
if
false
&&
err
==
nil
&&
!
ctxt
.
isDir
(
p
.
Dir
)
{
err
=
fmt
.
Errorf
(
"%q is not a directory"
,
p
.
Dir
)
}
return
p
,
err
return
ctxt
.
Import
(
"."
,
dir
,
mode
)
}
// NoGoError is the error used by Import to describe a directory
...
...
libgo/go/go/build/build_test.go
View file @
777133fe
...
...
@@ -5,7 +5,6 @@
package
build
import
(
"fmt"
"os"
"path/filepath"
"runtime"
...
...
@@ -90,17 +89,6 @@ func TestLocalDirectory(t *testing.T) {
}
}
// golang.org/issue/3248
func
TestBogusDirectory
(
t
*
testing
.
T
)
{
return
// See issue 4696.
const
dir
=
"/foo/bar/baz/gopher"
_
,
err
:=
ImportDir
(
dir
,
FindOnly
)
want
:=
fmt
.
Sprintf
(
"%q is not a directory"
,
filepath
.
FromSlash
(
dir
))
if
err
==
nil
||
err
.
Error
()
!=
want
{
t
.
Errorf
(
"got error %q, want %q"
,
err
,
want
)
}
}
func
TestShouldBuild
(
t
*
testing
.
T
)
{
const
file1
=
"// +build tag1
\n\n
"
+
"package main
\n
"
...
...
libgo/runtime/go-traceback.c
View file @
777133fe
...
...
@@ -17,11 +17,11 @@ runtime_traceback ()
int32
c
;
c
=
runtime_callers
(
1
,
pcbuf
,
sizeof
pcbuf
/
sizeof
pcbuf
[
0
]);
runtime_printtrace
(
pcbuf
,
c
);
runtime_printtrace
(
pcbuf
,
c
,
true
);
}
void
runtime_printtrace
(
uintptr
*
pcbuf
,
int32
c
)
runtime_printtrace
(
uintptr
*
pcbuf
,
int32
c
,
bool
current
)
{
int32
i
;
...
...
@@ -32,7 +32,7 @@ runtime_printtrace (uintptr *pcbuf, int32 c)
intgo
line
;
if
(
__go_file_line
(
pcbuf
[
i
],
&
fn
,
&
file
,
&
line
)
&&
runtime_showframe
(
fn
))
&&
runtime_showframe
(
fn
,
current
))
{
runtime_printf
(
"%S
\n
"
,
fn
);
runtime_printf
(
"
\t
%S:%D
\n
"
,
file
,
(
int64
)
line
);
...
...
libgo/runtime/panic.c
View file @
777133fe
...
...
@@ -86,6 +86,11 @@ runtime_dopanic(int32 unused __attribute__ ((unused)))
void
runtime_throw
(
const
char
*
s
)
{
M
*
mp
;
mp
=
runtime_m
();
if
(
mp
->
throwing
==
0
)
mp
->
throwing
=
1
;
runtime_startpanic
();
runtime_printf
(
"fatal error: %s
\n
"
,
s
);
runtime_dopanic
(
0
);
...
...
libgo/runtime/proc.c
View file @
777133fe
...
...
@@ -528,6 +528,7 @@ runtime_main(void)
setmcpumax
(
runtime_gomaxprocs
);
runtime_sched
.
init
=
true
;
scvg
=
__go_go
(
runtime_MHeap_Scavenger
,
nil
);
scvg
->
issystem
=
true
;
main_init
();
runtime_sched
.
init
=
false
;
if
(
!
runtime_sched
.
lockmain
)
...
...
@@ -638,12 +639,16 @@ void
runtime_tracebackothers
(
G
*
volatile
me
)
{
G
*
volatile
gp
;
Traceback
traceback
;
Traceback
tb
;
int32
traceback
;
traceback
.
gp
=
me
;
tb
.
gp
=
me
;
traceback
=
runtime_gotraceback
();
for
(
gp
=
runtime_allg
;
gp
!=
nil
;
gp
=
gp
->
alllink
)
{
if
(
gp
==
me
||
gp
->
status
==
Gdead
)
continue
;
if
(
gp
->
issystem
&&
traceback
<
2
)
continue
;
runtime_printf
(
"
\n
"
);
runtime_goroutineheader
(
gp
);
...
...
@@ -661,7 +666,7 @@ runtime_tracebackothers(G * volatile me)
continue
;
}
gp
->
traceback
=
&
t
raceback
;
gp
->
traceback
=
&
t
b
;
#ifdef USING_SPLIT_STACK
__splitstack_getcontext
(
&
me
->
stack_context
[
0
]);
...
...
@@ -672,7 +677,7 @@ runtime_tracebackothers(G * volatile me)
runtime_gogo
(
gp
);
}
runtime_printtrace
(
t
raceback
.
pcbuf
,
traceback
.
c
);
runtime_printtrace
(
t
b
.
pcbuf
,
tb
.
c
,
false
);
runtime_goroutinetrailer
(
gp
);
}
}
...
...
@@ -975,6 +980,7 @@ top:
if
((
scvg
==
nil
&&
runtime_sched
.
grunning
==
0
)
||
(
scvg
!=
nil
&&
runtime_sched
.
grunning
==
1
&&
runtime_sched
.
gwait
==
0
&&
(
scvg
->
status
==
Grunning
||
scvg
->
status
==
Gsyscall
)))
{
m
->
throwing
=
-
1
;
// do not dump full stacks
runtime_throw
(
"all goroutines are asleep - deadlock!"
);
}
...
...
libgo/runtime/runtime.c
View file @
777133fe
...
...
@@ -132,10 +132,12 @@ runtime_cputicks(void)
}
bool
runtime_showframe
(
String
s
)
runtime_showframe
(
String
s
,
bool
current
)
{
static
int32
traceback
=
-
1
;
if
(
current
&&
runtime_m
()
->
throwing
>
0
)
return
1
;
if
(
traceback
<
0
)
traceback
=
runtime_gotraceback
();
return
traceback
>
1
||
(
__builtin_memchr
(
s
.
str
,
'.'
,
s
.
len
)
!=
nil
&&
__builtin_memcmp
(
s
.
str
,
"runtime."
,
7
)
!=
0
);
...
...
libgo/runtime/runtime.h
View file @
777133fe
...
...
@@ -178,6 +178,7 @@ struct G
G
*
schedlink
;
bool
readyonstop
;
bool
ispanic
;
bool
issystem
;
int8
raceignore
;
// ignore race detection events
M
*
m
;
// for debuggers, but offset not hard-coded
M
*
lockedm
;
...
...
@@ -208,6 +209,7 @@ struct M
G
*
curg
;
// current running goroutine
int32
id
;
int32
mallocing
;
int32
throwing
;
int32
gcing
;
int32
locks
;
int32
nomemprof
;
...
...
@@ -389,7 +391,7 @@ void runtime_goroutineheader(G*);
void
runtime_goroutinetrailer
(
G
*
);
void
runtime_traceback
();
void
runtime_tracebackothers
(
G
*
);
void
runtime_printtrace
(
uintptr
*
,
int32
);
void
runtime_printtrace
(
uintptr
*
,
int32
,
bool
);
String
runtime_gostringnocopy
(
const
byte
*
);
void
*
runtime_mstart
(
void
*
);
G
*
runtime_malg
(
int32
,
byte
**
,
size_t
*
);
...
...
@@ -593,7 +595,7 @@ void runtime_osyield(void);
void
runtime_LockOSThread
(
void
)
__asm__
(
GOSYM_PREFIX
"runtime.LockOSThread"
);
void
runtime_UnlockOSThread
(
void
)
__asm__
(
GOSYM_PREFIX
"runtime.UnlockOSThread"
);
bool
runtime_showframe
(
String
);
bool
runtime_showframe
(
String
,
bool
);
uintptr
runtime_memlimit
(
void
);
...
...
libgo/runtime/time.goc
View file @
777133fe
...
...
@@ -110,8 +110,10 @@ addtimer(Timer *t)
runtime_ready(timers.timerproc);
}
}
if(timers.timerproc == nil)
if(timers.timerproc == nil)
{
timers.timerproc = __go_go(timerproc, nil);
timers.timerproc->issystem = true;
}
}
// Delete timer t from the heap.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment