Commit 777133fe by Ian Lance Taylor

libgo: Update Go library to master revision 15502/229081515358.

From-SVN: r195569
parent 900f0840
921e53d4863c 229081515358
The first line of this file holds the Mercurial revision number of the The first line of this file holds the Mercurial revision number of the
last merge done from the master library sources. last merge done from the master library sources.
...@@ -153,5 +153,37 @@ func BenchmarkCodeUnmarshalReuse(b *testing.B) { ...@@ -153,5 +153,37 @@ func BenchmarkCodeUnmarshalReuse(b *testing.B) {
b.Fatal("Unmmarshal:", err) 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)
}
}
} }
...@@ -52,25 +52,6 @@ import ( ...@@ -52,25 +52,6 @@ import (
// an UnmarshalTypeError describing the earliest such error. // an UnmarshalTypeError describing the earliest such error.
// //
func Unmarshal(data []byte, v interface{}) 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) d := new(decodeState).init(data)
// Quick check for well-formedness. // Quick check for well-formedness.
......
...@@ -1059,12 +1059,33 @@ func TestUnmarshalTypeError(t *testing.T) { ...@@ -1059,12 +1059,33 @@ func TestUnmarshalTypeError(t *testing.T) {
for _, item := range decodeTypeErrorTests { for _, item := range decodeTypeErrorTests {
err := Unmarshal([]byte(item.src), item.dest) err := Unmarshal([]byte(item.src), item.dest)
if _, ok := err.(*UnmarshalTypeError); !ok { 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) 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. // Test handling of unexported fields that should be ignored.
// Issue 4660 // Issue 4660
type unexportedFields struct { type unexportedFields struct {
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package json package json
import ( import (
"bytes"
"errors" "errors"
"io" "io"
) )
...@@ -58,6 +59,12 @@ func (dec *Decoder) Decode(v interface{}) error { ...@@ -58,6 +59,12 @@ func (dec *Decoder) Decode(v interface{}) error {
return err 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. // readValue reads a JSON value into dec.buf.
// It returns the length of the encoding. // It returns the length of the encoding.
func (dec *Decoder) readValue() (int, error) { func (dec *Decoder) readValue() (int, error) {
......
...@@ -6,8 +6,10 @@ package json ...@@ -6,8 +6,10 @@ package json
import ( import (
"bytes" "bytes"
"io/ioutil"
"net" "net"
"reflect" "reflect"
"strings"
"testing" "testing"
) )
...@@ -83,6 +85,28 @@ func TestDecoder(t *testing.T) { ...@@ -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 { func nlines(s string, n int) string {
if n <= 0 { if n <= 0 {
return "" return ""
......
...@@ -5,10 +5,6 @@ package ssa ...@@ -5,10 +5,6 @@ package ssa
// TODO(adonovan): instead of creating several "unreachable" blocks // TODO(adonovan): instead of creating several "unreachable" blocks
// per function in the Builder, reuse a single one (e.g. at Blocks[1]) // per function in the Builder, reuse a single one (e.g. at Blocks[1])
// to reduce garbage. // 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 ( import (
"fmt" "fmt"
...@@ -117,7 +113,7 @@ func fuseBlocks(f *Function, a *BasicBlock) bool { ...@@ -117,7 +113,7 @@ func fuseBlocks(f *Function, a *BasicBlock) bool {
} }
// A inherits B's successors // 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. // Fix up Preds links of all successors of B.
for _, c := range b.Succs { for _, c := range b.Succs {
......
...@@ -151,16 +151,27 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock { ...@@ -151,16 +151,27 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock {
func (f *Function) addParam(name string, typ types.Type) *Parameter { func (f *Function) addParam(name string, typ types.Type) *Parameter {
v := &Parameter{ v := &Parameter{
Name_: name, Name_: name,
Type_: pointer(typ), // address of param Type_: typ,
} }
f.Params = append(f.Params, v) f.Params = append(f.Params, v)
return v return v
} }
func (f *Function) addObjParam(obj types.Object) *Parameter { // addSpilledParam declares a parameter that is pre-spilled to the
p := f.addParam(obj.GetName(), obj.GetType()) // stack; the function body will load/store the spilled location.
f.objects[obj] = p // Subsequent registerization will eliminate spills where possible.
return p //
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. // 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) { ...@@ -186,7 +197,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if f.syntax.recvField != nil { if f.syntax.recvField != nil {
for _, field := range f.syntax.recvField.List { for _, field := range f.syntax.recvField.List {
for _, n := range field.Names { for _, n := range field.Names {
f.addObjParam(idents[n]) f.addSpilledParam(idents[n])
} }
if field.Names == nil { if field.Names == nil {
f.addParam(f.Signature.Recv.Name, f.Signature.Recv.Type) 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) { ...@@ -198,7 +209,7 @@ func (f *Function) start(mode BuilderMode, idents map[*ast.Ident]types.Object) {
if f.syntax.paramFields != nil { if f.syntax.paramFields != nil {
for _, field := range f.syntax.paramFields.List { for _, field := range f.syntax.paramFields.List {
for _, n := range field.Names { for _, n := range field.Names {
f.addObjParam(idents[n]) f.addSpilledParam(idents[n])
} }
} }
} }
...@@ -300,18 +311,18 @@ func (f *Function) addLocal(typ types.Type) *Alloc { ...@@ -300,18 +311,18 @@ func (f *Function) addLocal(typ types.Type) *Alloc {
func (f *Function) lookup(obj types.Object, escaping bool) Value { func (f *Function) lookup(obj types.Object, escaping bool) Value {
if v, ok := f.objects[obj]; ok { if v, ok := f.objects[obj]; ok {
if escaping { if escaping {
switch v := v.(type) { // Walk up the chain of Captures.
case *Capture: x := v
// TODO(adonovan): fix: we must support this case. for {
// Requires copying to a 'new' Alloc. if c, ok := x.(*Capture); ok {
fmt.Fprintln(os.Stderr, "Error: escaping reference to Capture") x = c.Outer
case *Parameter: } else {
v.Heap = true break
case *Alloc: }
v.Heap = true
default:
panic(fmt.Sprintf("Unexpected Function.objects kind: %T", v))
} }
// 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) return v // function-local var (address)
} }
...@@ -340,7 +351,7 @@ func (f *Function) emit(instr Instruction) Value { ...@@ -340,7 +351,7 @@ func (f *Function) emit(instr Instruction) Value {
func (f *Function) DumpTo(w io.Writer) { func (f *Function) DumpTo(w io.Writer) {
fmt.Fprintf(w, "# Name: %s\n", f.FullName()) fmt.Fprintf(w, "# Name: %s\n", f.FullName())
fmt.Fprintf(w, "# Declared at %s\n", f.Prog.Files.Position(f.Pos)) 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 { if f.Enclosing != nil {
fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name()) fmt.Fprintf(w, "# Parent: %s\n", f.Enclosing.Name())
...@@ -411,6 +422,7 @@ func (f *Function) newBasicBlock(name string) *BasicBlock { ...@@ -411,6 +422,7 @@ func (f *Function) newBasicBlock(name string) *BasicBlock {
Name: fmt.Sprintf("%d.%s", len(f.Blocks), name), Name: fmt.Sprintf("%d.%s", len(f.Blocks), name),
Func: f, Func: f,
} }
b.Succs = b.succs2[:0]
f.Blocks = append(f.Blocks, b) f.Blocks = append(f.Blocks, b)
return b return b
} }
...@@ -246,19 +246,20 @@ type Function struct { ...@@ -246,19 +246,20 @@ type Function struct {
// instructions, respectively). // instructions, respectively).
// //
type BasicBlock struct { type BasicBlock struct {
Name string // label; no semantic significance Name string // label; no semantic significance
Func *Function // containing function Func *Function // containing function
Instrs []Instruction // instructions in order Instrs []Instruction // instructions in order
Preds, Succs []*BasicBlock // predecessors and successors Preds, Succs []*BasicBlock // predecessors and successors
succs2 [2]*BasicBlock // initial space for Succs.
} }
// Pure values ---------------------------------------- // Pure values ----------------------------------------
// A Capture is a pointer to a lexically enclosing local variable. // A Capture is a pointer to a lexically enclosing local variable.
// //
// The referent of a capture is a Parameter, Alloc or another Capture // The referent of a capture is an Alloc or another Capture and is
// and is always considered potentially escaping, so Captures are // always considered potentially escaping, so Captures are always
// always addresses in the heap, and have pointer types. // addresses in the heap, and have pointer types.
// //
type Capture struct { type Capture struct {
Outer Value // the Value captured from the enclosing context. Outer Value // the Value captured from the enclosing context.
...@@ -266,22 +267,9 @@ type Capture struct { ...@@ -266,22 +267,9 @@ type Capture struct {
// A Parameter represents an input parameter of a function. // 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 { type Parameter struct {
Name_ string Name_ string
Type_ *types.Pointer Type_ types.Type
Heap bool
} }
// A Literal represents a literal nil, boolean, string or numeric // A Literal represents a literal nil, boolean, string or numeric
......
...@@ -321,13 +321,7 @@ func (p *Package) IsCommand() bool { ...@@ -321,13 +321,7 @@ func (p *Package) IsCommand() bool {
// ImportDir is like Import but processes the Go package found in // ImportDir is like Import but processes the Go package found in
// the named directory. // the named directory.
func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) { func (ctxt *Context) ImportDir(dir string, mode ImportMode) (*Package, error) {
p, err := ctxt.Import(".", dir, mode) return 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
} }
// NoGoError is the error used by Import to describe a directory // NoGoError is the error used by Import to describe a directory
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package build package build
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
...@@ -90,17 +89,6 @@ func TestLocalDirectory(t *testing.T) { ...@@ -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) { func TestShouldBuild(t *testing.T) {
const file1 = "// +build tag1\n\n" + const file1 = "// +build tag1\n\n" +
"package main\n" "package main\n"
......
...@@ -17,11 +17,11 @@ runtime_traceback () ...@@ -17,11 +17,11 @@ runtime_traceback ()
int32 c; int32 c;
c = runtime_callers (1, pcbuf, sizeof pcbuf / sizeof pcbuf[0]); c = runtime_callers (1, pcbuf, sizeof pcbuf / sizeof pcbuf[0]);
runtime_printtrace (pcbuf, c); runtime_printtrace (pcbuf, c, true);
} }
void void
runtime_printtrace (uintptr *pcbuf, int32 c) runtime_printtrace (uintptr *pcbuf, int32 c, bool current)
{ {
int32 i; int32 i;
...@@ -32,7 +32,7 @@ runtime_printtrace (uintptr *pcbuf, int32 c) ...@@ -32,7 +32,7 @@ runtime_printtrace (uintptr *pcbuf, int32 c)
intgo line; intgo line;
if (__go_file_line (pcbuf[i], &fn, &file, &line) if (__go_file_line (pcbuf[i], &fn, &file, &line)
&& runtime_showframe (fn)) && runtime_showframe (fn, current))
{ {
runtime_printf ("%S\n", fn); runtime_printf ("%S\n", fn);
runtime_printf ("\t%S:%D\n", file, (int64) line); runtime_printf ("\t%S:%D\n", file, (int64) line);
......
...@@ -86,6 +86,11 @@ runtime_dopanic(int32 unused __attribute__ ((unused))) ...@@ -86,6 +86,11 @@ runtime_dopanic(int32 unused __attribute__ ((unused)))
void void
runtime_throw(const char *s) runtime_throw(const char *s)
{ {
M *mp;
mp = runtime_m();
if(mp->throwing == 0)
mp->throwing = 1;
runtime_startpanic(); runtime_startpanic();
runtime_printf("fatal error: %s\n", s); runtime_printf("fatal error: %s\n", s);
runtime_dopanic(0); runtime_dopanic(0);
......
...@@ -528,6 +528,7 @@ runtime_main(void) ...@@ -528,6 +528,7 @@ runtime_main(void)
setmcpumax(runtime_gomaxprocs); setmcpumax(runtime_gomaxprocs);
runtime_sched.init = true; runtime_sched.init = true;
scvg = __go_go(runtime_MHeap_Scavenger, nil); scvg = __go_go(runtime_MHeap_Scavenger, nil);
scvg->issystem = true;
main_init(); main_init();
runtime_sched.init = false; runtime_sched.init = false;
if(!runtime_sched.lockmain) if(!runtime_sched.lockmain)
...@@ -638,12 +639,16 @@ void ...@@ -638,12 +639,16 @@ void
runtime_tracebackothers(G * volatile me) runtime_tracebackothers(G * volatile me)
{ {
G * volatile gp; 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) { for(gp = runtime_allg; gp != nil; gp = gp->alllink) {
if(gp == me || gp->status == Gdead) if(gp == me || gp->status == Gdead)
continue; continue;
if(gp->issystem && traceback < 2)
continue;
runtime_printf("\n"); runtime_printf("\n");
runtime_goroutineheader(gp); runtime_goroutineheader(gp);
...@@ -661,7 +666,7 @@ runtime_tracebackothers(G * volatile me) ...@@ -661,7 +666,7 @@ runtime_tracebackothers(G * volatile me)
continue; continue;
} }
gp->traceback = &traceback; gp->traceback = &tb;
#ifdef USING_SPLIT_STACK #ifdef USING_SPLIT_STACK
__splitstack_getcontext(&me->stack_context[0]); __splitstack_getcontext(&me->stack_context[0]);
...@@ -672,7 +677,7 @@ runtime_tracebackothers(G * volatile me) ...@@ -672,7 +677,7 @@ runtime_tracebackothers(G * volatile me)
runtime_gogo(gp); runtime_gogo(gp);
} }
runtime_printtrace(traceback.pcbuf, traceback.c); runtime_printtrace(tb.pcbuf, tb.c, false);
runtime_goroutinetrailer(gp); runtime_goroutinetrailer(gp);
} }
} }
...@@ -975,6 +980,7 @@ top: ...@@ -975,6 +980,7 @@ top:
if((scvg == nil && runtime_sched.grunning == 0) || if((scvg == nil && runtime_sched.grunning == 0) ||
(scvg != nil && runtime_sched.grunning == 1 && runtime_sched.gwait == 0 && (scvg != nil && runtime_sched.grunning == 1 && runtime_sched.gwait == 0 &&
(scvg->status == Grunning || scvg->status == Gsyscall))) { (scvg->status == Grunning || scvg->status == Gsyscall))) {
m->throwing = -1; // do not dump full stacks
runtime_throw("all goroutines are asleep - deadlock!"); runtime_throw("all goroutines are asleep - deadlock!");
} }
......
...@@ -132,10 +132,12 @@ runtime_cputicks(void) ...@@ -132,10 +132,12 @@ runtime_cputicks(void)
} }
bool bool
runtime_showframe(String s) runtime_showframe(String s, bool current)
{ {
static int32 traceback = -1; static int32 traceback = -1;
if(current && runtime_m()->throwing > 0)
return 1;
if(traceback < 0) if(traceback < 0)
traceback = runtime_gotraceback(); traceback = runtime_gotraceback();
return traceback > 1 || (__builtin_memchr(s.str, '.', s.len) != nil && __builtin_memcmp(s.str, "runtime.", 7) != 0); return traceback > 1 || (__builtin_memchr(s.str, '.', s.len) != nil && __builtin_memcmp(s.str, "runtime.", 7) != 0);
......
...@@ -178,6 +178,7 @@ struct G ...@@ -178,6 +178,7 @@ struct G
G* schedlink; G* schedlink;
bool readyonstop; bool readyonstop;
bool ispanic; bool ispanic;
bool issystem;
int8 raceignore; // ignore race detection events int8 raceignore; // ignore race detection events
M* m; // for debuggers, but offset not hard-coded M* m; // for debuggers, but offset not hard-coded
M* lockedm; M* lockedm;
...@@ -208,6 +209,7 @@ struct M ...@@ -208,6 +209,7 @@ struct M
G* curg; // current running goroutine G* curg; // current running goroutine
int32 id; int32 id;
int32 mallocing; int32 mallocing;
int32 throwing;
int32 gcing; int32 gcing;
int32 locks; int32 locks;
int32 nomemprof; int32 nomemprof;
...@@ -389,7 +391,7 @@ void runtime_goroutineheader(G*); ...@@ -389,7 +391,7 @@ void runtime_goroutineheader(G*);
void runtime_goroutinetrailer(G*); void runtime_goroutinetrailer(G*);
void runtime_traceback(); void runtime_traceback();
void runtime_tracebackothers(G*); void runtime_tracebackothers(G*);
void runtime_printtrace(uintptr*, int32); void runtime_printtrace(uintptr*, int32, bool);
String runtime_gostringnocopy(const byte*); String runtime_gostringnocopy(const byte*);
void* runtime_mstart(void*); void* runtime_mstart(void*);
G* runtime_malg(int32, byte**, size_t*); G* runtime_malg(int32, byte**, size_t*);
...@@ -593,7 +595,7 @@ void runtime_osyield(void); ...@@ -593,7 +595,7 @@ void runtime_osyield(void);
void runtime_LockOSThread(void) __asm__ (GOSYM_PREFIX "runtime.LockOSThread"); void runtime_LockOSThread(void) __asm__ (GOSYM_PREFIX "runtime.LockOSThread");
void runtime_UnlockOSThread(void) __asm__ (GOSYM_PREFIX "runtime.UnlockOSThread"); void runtime_UnlockOSThread(void) __asm__ (GOSYM_PREFIX "runtime.UnlockOSThread");
bool runtime_showframe(String); bool runtime_showframe(String, bool);
uintptr runtime_memlimit(void); uintptr runtime_memlimit(void);
......
...@@ -110,8 +110,10 @@ addtimer(Timer *t) ...@@ -110,8 +110,10 @@ addtimer(Timer *t)
runtime_ready(timers.timerproc); runtime_ready(timers.timerproc);
} }
} }
if(timers.timerproc == nil) if(timers.timerproc == nil) {
timers.timerproc = __go_go(timerproc, nil); timers.timerproc = __go_go(timerproc, nil);
timers.timerproc->issystem = true;
}
} }
// Delete timer t from the heap. // Delete timer t from the heap.
......
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