Root/kernel/alloc.ccp

1#pypp 0
2// Iris: micro-kernel for a capability-based operating system.
3// alloc.ccp: Allocation of kernel structures.
4// Copyright 2009 Bas Wijnen <wijnen@debian.org>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19#include "kernel.hh"
20
21// Memory model used for kernel structure storage
22// Each Memory object has several pointers, one for each type of objects it contains. These pointers are the start of double-linked lists.
23// Each object also has a NEXT and PREV pointer, which point to the next and previous object in the same page. These pointers are 0 for the first (or last) object in a page. There is no pointer to the first object, it always starts at page_base + SIZE.
24// The PREV/NEXT-list contains all objects in the page, including kFree objects, in the order they appear in the page.
25// The prev/next-lists contain only objects of one type, unsorted.
26// All pointers are to the start of the object. There is a header of size SIZE before it, containing NEXT and PREV.
27
28#define PREV(x) (((kObject **)(x))[-2])
29#define NEXT(x) (((kObject **)(x))[-1])
30// ATTENTION! When changing SIZE, be sure to also change the hard-coded 8 in kernel.hhp which defines MAX_NUM_CAPS.
31#define SIZE (2 * sizeof (kObject *))
32
33bool kMemory::use (unsigned num):
34    // Go up to parents, incrementing used.
35    for kMemory *m = this; m; m = m->address_space:
36        if used + num > limit:
37            // Not allowed. Restore used for all children.
38            for kMemory *r = this; r != m; r = r->address_space:
39                r->used -= num
40            return false
41        m->used += num
42    return true
43
44void kMemory::unuse (unsigned num):
45    for kMemory *m = this; m; m = m->address_space:
46        m->used -= num
47
48// This allocates a new block of memory for use by the kernel.
49// size is the required size of the block (excluding SIZE)
50// first is a pointer to the first object pointer of this type.
51// The result is a block of size at least size, which is linked as an object in the list of first.
52void *kMemory::search_free (unsigned size, void **first):
53    kFree *f
54    if size >= PAGE_SIZE - SIZE:
55        panic (size, "requested size is too large")
56    int s = 0
57    // Let's see if there already is a kFree chunk which is large enough.
58    for f = frees; f; f = (kFree *)f->next:
59        if NEXT (f):
60            s = (unsigned)NEXT (f) - (unsigned)f
61        else:
62            s = PAGE_SIZE - ((unsigned)f & ~PAGE_MASK) + SIZE
63        // s is now the size of the current free block, including SIZE.
64        // The requirement is to fit a block of size, plus its SIZE header.
65        if s >= size + SIZE:
66            break
67    if !f:
68        // No chunk was found; allocate a new page and add a chunk in it. It is always large enough.
69        unsigned p = palloc ()
70        if !p:
71            kdebug ("no free space: kernel allocation failed")
72            return NULL
73        f = (kFree *)(p + SIZE)
74        // Mark it as a kFree object.
75        f->marker = ~0
76        // Link it in the kFree list.
77        f->next = frees
78        f->prev = NULL
79        frees = f
80        if f->next:
81            ((kFree *)f->next)->prev = f
82        // There are no other objects in this page.
83        NEXT (f) = NULL
84        PREV (f) = NULL
85        // The size of this block is the entire page.
86        s = PAGE_SIZE
87    // We have a free block, possibly too large. The block is linked in frees, and in the page.
88    if s >= size + sizeof (kFree) + 2 * SIZE:
89        // Create the new object at the end and keep the Free.
90        // f is the start of the free block
91        // f + (s - SIZE) is the end of the free block, compensated for the header of the next block.
92        // f + (s - SIZE) - size is the address where the new block should start.
93        kFree *obj = (kFree *)((unsigned)f + (s - SIZE) - size)
94        // Link the new object in the page.
95        NEXT (obj) = NEXT (f)
96        if NEXT (obj):
97            PREV (NEXT (obj)) = obj
98        PREV (obj) = f
99        NEXT (f) = obj
100        // Set f to the new object, because it is used by that name later.
101        f = obj
102    else:
103        // The block was only just large enough: turn it into a new type. It is already linked into the page.
104        // Unlink it from the free list.
105        if f->prev:
106            ((kFree *)f->prev)->next = f->next
107        else:
108            frees = (kFree *)f->next
109        if f->next:
110            ((kFree *)f->next)->prev = f->prev
111    // f is now a block which is linked in the page, but not in any list. Link it into first.
112    f->next = (kFree *)*first
113    f->prev = NULL
114    if f->next:
115        ((kFree *)f->next)->prev = f
116    *first = f
117    // Set common initial values.
118    f->address_space = this
119    f->refs.reset ()
120    return f
121
122// Free an object; it is still in its list, and it is still in the page list.
123void kMemory::free_obj (kObject *obj, kPointer *first):
124    kFree *self = (kFree *)obj
125    // Invalidate references.
126    while self->refs.valid ():
127        self->refs->invalidate ()
128    // Free it from its list.
129    if self->prev:
130        ((kFree *)self->prev)->next = self->next
131    else:
132        *(kPointer *)first = (kPointer)self->next
133    if self->next:
134        ((kFree *)self->next)->prev = self->prev
135    // Merge with previous, if it exists and is a kFree.
136    if PREV (self) && PREV (self)->is_free ():
137        self = (kFree *)PREV (self)
138        // Remove the object from the page list.
139        NEXT (self) = NEXT (obj)
140        if NEXT (self):
141            PREV (NEXT (self)) = self
142    else:
143        // The previous object is not a kFree, so create a new one.
144        // It is already linked in the page, but needs to be linked into the free list.
145        self->next = frees
146        self->prev = NULL
147        if self->next:
148            ((kFree *)self->next)->prev = self
149        frees = self
150        // Mark it as a kFree.
151        self->marker = ~0
152    // Merge with next, if it exists and is a kFree.
153    if NEXT (self) && NEXT (self)->is_free ():
154        // Unlink the next from the frees list.
155        kFree *n = (kFree *)NEXT (self)
156        if n->prev:
157            ((kFree *)n->prev)->next = n->next
158        else:
159            frees = (kFree *)n->next
160        if n->next:
161            ((kFree *)n->next)->prev = n->prev
162        // Unlink the next from the page list.
163        NEXT (self) = NEXT (NEXT (self))
164        if NEXT (self):
165            PREV (NEXT (self)) = self
166    // Free page if the resulting object is the only thing in it.
167    if !PREV (self) && !NEXT (self):
168        if self->next:
169            ((kFree *)self->next)->prev = self->prev
170        if self->prev:
171            ((kFree *)self->prev)->next = self->next
172        else:
173            frees = (kFree *)self->next
174        //kdebug ("freeing page: ")
175        //kdebug_num ((unsigned)self - SIZE)
176        //kdebug ("\n")
177        pfree ((unsigned)self - SIZE)
178
179kPage *kMemory::alloc_page ():
180    kPage *ret = (kPage *)search_free (sizeof (kPage), (void **)&pages)
181    if !ret:
182        return NULL
183    ret->frame = 0
184    ret->flags = 0
185    ret->mapping = ~0
186    ret->share_prev = NULL
187    ret->share_next = NULL
188    kPage_arch_init (ret)
189    return ret
190
191kThread *kMemory::alloc_thread (unsigned size):
192    kThread *ret = (kThread *)search_free (sizeof (kThread) + (size - 1) * sizeof (kThread::caps_store), (void **)&threads)
193    if !ret:
194        return NULL
195    ret->receivers = NULL
196    ret->pc = 0
197    ret->sp = 0
198    kThread_arch_init (ret)
199    ret->flags = 0
200    ret->id = ~0
201    ret->schedule_prev = NULL
202    ret->schedule_next = NULL
203    ret->slots = size
204    for unsigned i = 0; i < size; ++i:
205        ret->slot[i].prev.thread = NULL
206        ret->slot[i].next.thread = NULL
207        ret->slot[i].caps = NULL
208    //kdebug ("new thread: ")
209    //kdebug_num ((unsigned)ret)
210    //kdebug ("\n")
211    return ret
212
213void kCaps::init (unsigned s):
214    first_slot.thread = NULL
215    size = s
216    for unsigned i = 0; i < s; ++i:
217        set (i, NULL, 0, kCapRef (), NULL)
218
219kCaps *kMemory::alloc_caps (unsigned size):
220    if size == 0:
221        dpanic (0, "zero-size caps")
222        return NULL
223    kCaps *ret
224    if size > MAX_NUM_CAPS:
225        dpanic (size, "requested caps is too large")
226        return NULL
227    ret = (kCaps *)search_free (sizeof (kCaps) + (size - 1) * sizeof (kCapability), (void **)&capses)
228    if !ret:
229        return NULL
230    ret->init (size)
231    //kdebug ("allocate caps ")
232    //kdebug_num ((unsigned)ret)
233    //kdebug ('+')
234    //kdebug_num (size)
235    //kdebug ('\n')
236    return ret
237
238kMessage *kMemory::alloc_message (kReceiver *target):
239    kMessage *ret = (kMessage *)search_free (sizeof (kMessage) + sizeof (kCapability), (void **)&target->messages)
240    if !ret:
241        return NULL
242    ret->caps.init (2)
243    if !ret->next:
244        target->last_message = ret
245    return ret
246
247kList *kMemory::alloc_list ():
248    kList *ret = (kList *)search_free (sizeof (kList), (void **)&lists)
249    if !ret:
250        return NULL
251    ret->owner.init (1)
252    ret->first_listitem = NULL
253    return ret
254
255kListitem *kMemory::alloc_listitem ():
256    kListitem *ret = (kListitem *)search_free (sizeof (kListitem), (void **)&listitems)
257    if !ret:
258        return NULL
259    ret->target.init (1)
260    ret->list = NULL
261    ret->prev_item = NULL
262    ret->next_item = NULL
263    ret->info = 0
264    return ret
265
266kReceiver *kMemory::alloc_receiver ():
267    kReceiver *ret = (kReceiver *)search_free (sizeof (kReceiver), (void **)&receivers)
268    if !ret:
269        return NULL
270    ret->owner = NULL
271    ret->prev_owned = NULL
272    ret->next_owned = NULL
273    ret->alarm_count = ~0
274    ret->caps = NULL
275    ret->capabilities.reset ()
276    ret->messages = NULL
277    ret->last_message = NULL
278    ret->reply_protected_data = ~0
279    ret->protected_only = false
280    ret->queue_limit = ~0
281    return ret
282
283kMemory *kMemory::alloc_memory ():
284    kMemory *ret = (kMemory *)search_free (sizeof (kMemory), (void **)&memories)
285    if !ret:
286        return NULL
287    ret->frees = NULL
288    ret->pages = NULL
289    ret->threads = NULL
290    ret->capses = NULL
291    ret->receivers = NULL
292    ret->memories = NULL
293    ret->limit = ~0
294    ret->used = 0
295    kMemory_arch_init (ret)
296    return ret
297
298void kCaps::set (unsigned index, kReceiver *target, Iris::Num pdata, kCapRef parent, kCapRef *parent_ptr):
299    if index >= size:
300        kdebug ("size: ")
301        kdebug_num (size)
302        kdebug ("\n")
303        dpanic (index, "index too large for kCaps")
304        return
305    kCapability *c = &caps[index]
306    c->target = target
307    c->protected_data = pdata
308    c->parent = parent
309    c->children.reset ()
310    c->sibling_prev.reset ()
311    if parent.valid ():
312        c->sibling_next = parent->children
313        parent->children = kCapRef (this, index)
314    else:
315        if parent_ptr:
316            c->sibling_next = *parent_ptr
317            *parent_ptr = kCapRef (this, index)
318        else:
319            c->sibling_next.reset ()
320    if c->sibling_next.valid ():
321        c->sibling_next->sibling_prev = kCapRef (this, index)
322
323void kCaps::clone (unsigned index, kCapRef source, bool copy):
324    cap (index)->invalidate ()
325    if !source.valid ():
326        return
327    if copy:
328        if source->parent.valid ():
329            set (index, source->target, source->protected_data, source->parent)
330        else if (unsigned)source->target & ~KERNEL_MASK:
331            set (index, source->target, source->protected_data, kCapRef (), &source->target->capabilities)
332        else:
333            set (index, source->target, source->protected_data, kCapRef (), &((kObject *)source->protected_data.l)->refs)
334    else:
335        set (index, source->target, source->protected_data, source)
336
337void kMemory::free_page (kPage *page):
338    if page->mapping != ~0:
339        page->address_space->unmap (page)
340    page->forget ()
341    if page->flags & Iris::Page::PAYING:
342        unuse ()
343    free_obj (page, (kPointer *)&pages)
344
345void kThread::unset_slot (unsigned s):
346    if !slot[s].caps:
347        return
348    if slot[s].prev.thread:
349        slot[s].prev.thread->slot[slot[s].prev.index].next = slot[s].next
350    else:
351        slot[s].caps->first_slot = slot[s].next
352    if slot[s].next.thread:
353        slot[s].next.thread->slot[slot[s].next.index].prev = slot[s].prev
354    slot[s].prev.thread = NULL
355    slot[s].next.thread = NULL
356    slot[s].caps = NULL
357
358void kMemory::free_thread (kThread *thread):
359    thread->unrun ()
360    while thread->receivers:
361        thread->receivers->orphan ()
362    for unsigned i = 0; i < thread->slots; ++i:
363        thread->unset_slot (i)
364    free_obj (thread, (void **)&threads)
365    if old_current == thread:
366        old_current = NULL
367
368void kMemory::free_message (kReceiver *owner, kMessage *message):
369    for unsigned i = 0; i < 2; ++i:
370        message->caps.cap (i)->invalidate ()
371    if !message->next:
372        owner->last_message = (kMessageP)message->prev
373    free_obj (message, (void **)&owner->messages)
374
375void kMemory::free_receiver (kReceiver *receiver):
376    receiver->orphan ()
377    while receiver->capabilities.valid ():
378        receiver->capabilities->invalidate ()
379    while receiver->messages:
380        free_message (receiver, receiver->messages)
381    free_obj (receiver, (void **)&receivers)
382
383void kReceiver::orphan ():
384    if prev_owned:
385        prev_owned->next_owned = next_owned
386    else if owner:
387        owner->receivers = next_owned
388    if next_owned:
389        next_owned->prev_owned = prev_owned
390    owner = NULL
391
392void kReceiver::own (kThread *o):
393    if owner:
394        orphan ()
395    owner = o
396    next_owned = o->receivers
397    if next_owned:
398        next_owned->prev_owned = this
399    o->receivers = this
400
401void kCapability::invalidate ():
402    if !target:
403        return
404    //kdebug_num ((unsigned)this)
405    //kdebug ("\n")
406    //kdebug_num ((unsigned)target)
407    //kdebug (":")
408    //kdebug_num ((unsigned)protected_data.l)
409    //kdebug ("\n")
410    if (unsigned)this == dbg_code.h:
411        dpanic (0, "invalidating watched capability")
412    if sibling_prev.valid ():
413        sibling_prev->sibling_next = sibling_next
414    else if parent.valid ():
415        parent->children = sibling_next
416    else if (unsigned)target & ~KERNEL_MASK:
417        target->capabilities = sibling_next
418    else:
419        ((kObject *)protected_data.l)->refs = sibling_next
420    if sibling_next.valid ():
421        sibling_next->sibling_prev = sibling_prev
422    parent.reset ()
423    sibling_prev.reset ()
424    sibling_next.reset ()
425    kCapability *c = this
426    while c:
427        while c->children.valid ():
428            c = c->children.deref ()
429        kCapability *next = c->sibling_next.deref ()
430        if !next:
431            next = c->parent.deref ()
432        c->target = NULL
433        if c->parent.valid ():
434            c->parent->children = c->sibling_next
435        c->parent.reset ()
436        c->children.reset ()
437        c->sibling_prev.reset ()
438        c->sibling_next.reset ()
439        c->protected_data = 0
440        c = next
441
442void kMemory::free_caps (kCaps *c):
443    //kdebug ("free caps ")
444    //kdebug_num ((unsigned)c)
445    //kdebug ('\n')
446    for unsigned i = 0; i < c->size; ++i:
447        c->cap (i)->invalidate ()
448    while c->first_slot.thread:
449        c->first_slot.thread->unset_slot (c->first_slot.index)
450    free_obj (c, (void **)&capses)
451
452void kListitem::add (kList *l):
453    // Remove item from list.
454    if list:
455        if prev_item:
456            prev_item->next_item = next_item
457        else:
458            list->first_listitem = next_item
459        if next_item:
460            next_item->prev_item = prev_item
461        // Notify list owner.
462        if list->owner.cap (0):
463            kCapability::Context context
464            context.data[0] = list->first_listitem != NULL
465            context.data[1] = info
466            list->owner.cap (0)->invoke (&context)
467        // Don't leak info to new owner.
468        info = 0
469    list = l
470    prev_item = NULL
471    if !l:
472        next_item = NULL
473        return
474    next_item = l->first_listitem
475    l->first_listitem = this
476    if next_item:
477        next_item->prev_item = this
478
479void kMemory::free_listitem (kListitem *i):
480    // Unset target.
481    i->target.cap (0)->invalidate ()
482    // Remove item from its list.
483    i->add (NULL)
484    // Remove item from its address space.
485    free_obj (i, (void **)&listitems)
486
487void kMemory::free_list (kList *l):
488    // Unset callback.
489    l->owner.cap (0)->invalidate ()
490    // Clear list.
491    while l->first_listitem:
492        l->first_listitem->add (NULL)
493    // Remove list from address space.
494    free_obj (l, (void **)&lists)
495
496void kMemory::free_memory (kMemory *mem):
497    while mem->pages:
498        //kdebug ("freeing page ")
499        //kdebug_num ((unsigned)mem->pages)
500        //kdebug (", next = ")
501        //kdebug_num ((unsigned)mem->pages->next)
502        //kdebug ("\n")
503        mem->free_page (mem->pages)
504    while mem->capses:
505        mem->free_caps (mem->capses)
506    while mem->threads:
507        mem->free_thread (mem->threads)
508    while mem->memories:
509        mem->free_memory (mem->memories)
510    while mem->receivers:
511        mem->free_receiver (mem->receivers)
512    while mem->lists:
513        mem->free_list (mem->lists)
514    while mem->listitems:
515        mem->free_listitem (mem->listitems)
516    if mem->frees:
517        panic (0, "kernel memory leak: memory still in use")
518    free_obj (mem, (void **)&memories)
519
520void kPage::check_payment ():
521    kPage *p
522    for p = this; p; p = p->share_prev:
523        if p->flags & Iris::Page::PAYING:
524            return
525    for p = share_next; p; p = p->share_next:
526        if p->flags & Iris::Page::PAYING:
527            return
528    // No kPage is paying for this frame anymore.
529    raw_pfree (frame)
530    kPage *next
531    for p = share_prev, next = (p ? p->share_prev : NULL); p; p = next, next = p ? p->share_prev : NULL:
532        p->frame = NULL
533        p->share_prev = NULL
534        p->share_next = NULL
535        p->flags &= ~(Iris::Page::SHARED | Iris::Page::FRAME)
536        kPage_arch_update_mapping (p)
537    for p = this, next = p->share_next; p; p = next, next = p->share_next:
538        p->frame = NULL
539        p->share_prev = NULL
540        p->share_next = NULL
541        p->flags &= ~(Iris::Page::SHARED | Iris::Page::FRAME)
542        kPage_arch_update_mapping (p)
543
544void kPage::forget ():
545    if share_prev || share_next:
546        if share_prev:
547            share_prev->share_next = share_next
548        if share_next:
549            share_next->share_prev = share_prev
550            share_next->check_payment ()
551        else:
552            share_prev->check_payment ()
553        share_prev = NULL
554        share_next = NULL
555    else:
556        // If the page has a frame and should be freed, free it.
557        if !((flags ^ Iris::Page::FRAME) & (Iris::Page::PHYSICAL | Iris::Page::FRAME)):
558            raw_pfree (frame)
559    frame = 0
560    flags &= ~(Iris::Page::FRAME | Iris::Page::SHARED | Iris::Page::PHYSICAL | Iris::Page::UNCACHED)
561    kPage_arch_update_mapping (this)
562
563static void check_receiver (kReceiver *r, kCapRef cap, unsigned line):
564    if (unsigned)cap->target & ~KERNEL_MASK:
565        if cap->target != r:
566            dpanic (line, "consistency bug in capabilities")
567    else:
568        if cap->protected_data.l != (unsigned)r:
569            kdebug ("Buggy: receiver=")
570            kdebug_num ((unsigned)r)
571            kdebug ("; caps=")
572            kdebug_num ((unsigned)cap.caps)
573            kdebug ("; caps mem=")
574            kdebug_num ((unsigned)cap.caps->address_space)
575            kdebug ("; cap=")
576            kdebug_num ((unsigned)cap.deref ())
577            kdebug ("; cap target=")
578            kdebug_num ((unsigned)cap->target)
579            kdebug ("; protected=")
580            kdebug_num (cap->protected_data.l)
581            kdebug ("!= receiver\n")
582            dpanic (line, "consistency bug in kernel capabilities")
583    for kCapRef c = cap->children; c.valid (); c = c->sibling_next:
584        if c->protected_data.value () != cap->protected_data.value () || c->target != cap->target:
585            dpanic (line, "capability db bug")
586        check_receiver (r, c, line)
587
588void kReceiver::check (unsigned line):
589    for kCapRef cap = capabilities; cap.valid (); cap = cap->sibling_next:
590        check_receiver (this, cap, line)
591
592void kMemory::check (unsigned line):
593    for kReceiver *r = receivers; r; r = (kReceiver *)r->next:
594        r->check (line)
595    for kThread *t = threads; t; t = (kThread *)t->next:
596        if t->flags & Iris::Thread::RUNNING && t->pc == 0:
597            kdebug_num ((unsigned)t)
598            kdebug ("\n")
599            panic (line, "pc is 0")
600    for kMemory *m = memories; m; m = (kMemory *)m->next:
601        m->check (line)
602
603static void print_obj (kObject *o):
604    for kObject *obj = o; o; o = (kObject *)o->next:
605        kdebug_num ((unsigned)o)
606        kdebug ("->")
607    kdebug ("NULL\n")
608
609void kMemory::print (unsigned line, unsigned indent):
610    if indent == 0:
611        print_free ()
612    for unsigned i = 0; i < indent; ++i:
613        kdebug ('\t')
614    ++indent
615    kdebug ("Memory ")
616    kdebug_num ((unsigned)this)
617    kdebug ("\n")
618    for unsigned i = 0; i < indent; ++i:
619        kdebug ('\t')
620    kdebug ("frees: ")
621    for kFree *f = frees; f; f = (kFree *)f->next:
622        kdebug_num ((unsigned)f)
623        kdebug (":")
624        unsigned n = (unsigned)NEXT (f)
625        if n:
626            n -= (unsigned)f
627        if n >= PAGE_SIZE:
628            dpanic (0, "invalid kFree")
629        kdebug_num (n, 3)
630        kdebug ("->")
631    kdebug ("NULL\n")
632    for unsigned i = 0; i < indent; ++i:
633        kdebug ('\t')
634    kdebug ("pages: ")
635    print_obj (pages)
636    for unsigned i = 0; i < indent; ++i:
637        kdebug ('\t')
638    kdebug ("threads: ")
639    print_obj (threads)
640    for unsigned i = 0; i < indent; ++i:
641        kdebug ('\t')
642    kdebug ("receivers: ")
643    for kReceiver *r = receivers; r; r = (kReceiver *)r->next:
644        kdebug_num ((unsigned)r)
645        kdebug ("(")
646        for kMessage *m = r->messages; m; m = (kMessage *)m->next:
647            kdebug_num ((unsigned)m)
648            kdebug ("->")
649        kdebug ("NULL)->")
650    kdebug ("NULL\n")
651    for unsigned i = 0; i < indent; ++i:
652        kdebug ('\t')
653    kdebug ("capses: ")
654    print_obj (capses)
655    for kMemory *m = memories; m; m = (kMemory *)m->next:
656        m->print (line, indent)
657
658void check_impl (kObject *o, unsigned num, char const *msg):
659    for ; o; o = (kObject *)o->next:
660        unsigned n = (unsigned)NEXT (o)
661        unsigned size = n ? n - (unsigned)o : PAGE_SIZE - ((unsigned)o & PAGE_MASK)
662        if !check_free (o, size):
663            panic (num, msg)
664

Archive Download this file

Branches:
master



interactive