• Eine andere Möglichkeit wäre auch noch im Code sicher zu stellen, dass Objektreferenzen immer an mindestens einer Stelle erreichbar sind, dass würde den Overhead zum Tracking in C Code auch vermeiden:

    void stfield(vm_t* vm, literal_t key) {
    	value_t* obj = peek_value(vm->stack, 0);    // schaut sich das erste Element von oben an ohne pop
    	value_t* value = peek_value(vm->stack, 1);  // analog das zweite Element
    	
    	obj_set_field(obj, key, value);
    
    	pop_value(vm->stack);                       // Erst hier sind die Werte dann nicht mehr erreichbar
    	pop_value(vm->stack);
    }

    Das würde es auch für den später generierten JIT Code etwas einfacher machen, weil der sowieso eher noch optimiert wird und direkt auf den Speicher zugreift. Dann hätte man auch den Vorteil den fehleranfälligeren ASM Code minimal zu halten und dort keine Callbacks für den GC zu berücksichtigen.

    mov eax [stack_cur]  ; Speichere Wert an der Adresse stack_cur in eax
                         ; stack_cur ist die Adresse im stack und kann konstant zur JIT Zeit ausgerchnet werden 
                         ; und ist dann eine Konstante in x86 Code
    mov [field_addr] eax ; Speichere eax and die Stelle auf die field_addr zeigt
                         ; field_addr ist ebenfalls konstant (wird mit dem Objekt vom JIT ausgerechnet)
    sub stack_cur 16     ; Stackpointer wird verringert und damit sind die Werte auf dem Stack
                         ; erst hier nicht mehr erreichbar
    
  • Ich finde auch den Ansatz immer noch ganz interessant, die CIL Instruktionen atomar zu halten. Dann müsste man vor jeder Objektallokation einen Check machen ob der Thread gerade anhalten muss (Zum Beispiel, dass die Methode des GC die den Speicher herausgibt einen Fehler wirft). Und mann müsste natürlich regelmäßig (z.B. am Anfang eines Basicblocks) checken ob der Thread gerade anhalten soll. Entweder mit einer Fallunterscheidung oder einem Zugriff auf Speicher der unmapped wird, je nachdem was da performanter ist.

    Dieser Ansatz würde natürlich die Latenz bis der GC anlaufen kann etwas erhöhen, weil alle Threads erstmal in den Check laufen müssen. Es sollte sich aber in Grenzen halten, dadurch dass die Basicblocks nur begrenzt groß sein können.

  • Ich stimmt dir zu, dass es in dem einfachen Beispiel reichen würde die Werte länger als nötig auf dem Stack zu lassen, aber es gibt sicher auch Fälle in denen das nicht funktioniert bzw. sehr umständlich wird. Beispielweise bei Instruktionen, die Werte vom Stack nehmen und neue Werte draufschreiben:

    void castclass(vm_t* vm, class_t class) {
    	value_t* obj = peek_value(vm->stack, 0);
    	
    	value_t* casted = cast_obj_reference(obj, class);
    	
    	// obj darf erst vom Stack genommen werden sobald casted auf dem Stack gelandet ist
    	
    	push_value(vm->stack, casted);
    	
    	remove_from_stack(vm->stack, 1); // müsste mitten aus dem Stack löschen
    }

    Klar kann man sich für den Einzelfall wieder eine eigene Lösung ausdenken, aber ich find die allgemeine Lösung lesbarer und einfacher verständlich für Leute die neu zu dem Projekt dazu kommen:

    void castclass(vm_t* vm, class_t class) {
    	value_t* obj = pop_value(vm->stack);
    	
    	value_t* casted = cast_obj_reference(obj, class);
    	push_value(vm->stack, casted);
    	
    	unref_local(obj);
    	unref_local(casted);
    }

    bzw.

    void castclass(vm_t* vm, class_t class) {
    	scope_t scope;
    	
    	value_t* obj = pop_value(vm->stack, &scope);
    	
    	value_t* casted = cast_obj_reference(obj, class, &scope);
    	push_value(vm->stack, casted);
    	
    	leave_scope(&scope);
    }

    Vorteil von den Scopes: Wenn man bei jeder Allokation einen Scope übergeben muss ist sichergestellt dass man auch wirklich daran denkt ^^

    Zur Atomarität:

    Ich glaub wirklich nicht dass man CIL Instruktionen in der Praxis atomar halten kann.

    Nehmen wir an es gibt einen Keyboard-Interrupt-Handler der in CIL implementiert ist und bei einem Tastendruck den Wert vom Tastaturcontroller abholt, in ein allokiertes Objekt (sowas wie KeyboardEvent { keycode: .. }) steckt und das an eine Queue anhängt. Dann müsste man wirklich für jede Allokation darauf warten, dass alle Threads angehalten sind und sich gerade zwischen Instruktionen befinden. Das wär mit einer enormen Latenz verbunden und ich kann mir nicht vorstellen, dass man das in einem komplexen System, dass von mehreren Leuten entwickelt wird, wirklich verklemmungsfrei hinbekommt.

    Dazu kommt noch, dass ein optimierter JIT Instruktionen vielleicht nicht eine nach der anderen ausführt, sondern zusammenfasst oder sogar umordnet.

    Später gibts sicher noch mehr Funktionen die in nativem C geschrieben sind und die man von CIL aus aufrufen kann, z.B. um mit der Hardware zu interagieren. Die werden dann auch auf den CIL-Stack zugreifen und Objekte allokieren. Dort können wir nicht so einfach garantieren dass die Invarianten gelten. Nehmen wir an es gibt eine native Funktion die warten kann, z.B. um sich ein Lock zu holen. Schon ist die call-Instruktion nicht mehr unterbrechbar und andere Threads können nicht mehr allokieren.

    Ich würde das Problem lieber universell lösen anstatt dass man sich jetzt bei jedem push, jedem pop und jeder allokation gedanken machen muss ob man sich nicht gerade den Speicher zerschießt.

    Edited by Chris Hinze
  • Stimmt du hast Recht, dass das mit dem Stack irgendwann unschön werden kann.

    Bzgl. der Atomarität denk ich aber nicht, dass man immer alle Threads anhalten muss bei der Allokation eines Objekts, sondern nur falls der GC dann sagt dass kein Speicher mehr da ist. Würde allerdings auch wieder etwas Synchronisation bei mehreren Threads bedeuten.

    Das mit dem Warten (zumindest wenn aktiv gewartet wird) im native Code ist auf jeden Fall ein guter Punkt. Das wäre auch ein Spezialfall, der schwierig zu lösen ist.

    Ich denk, dann ist tatsächlich CIL atomar zu halten insgesamt komplizierter zu implementieren, als eine allgemeine Lösung.

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