Skip to content

cepsdev/planck-service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

planck-service

Smallest possible micro service - Combines compactness of portable machine code with the expressiveness of high level abstractions, and the power of mathematics.

STATUS: EXPERIMENTAL

The Idea

  • Abandon the delusion that there is a best language/method/paradigm to develop software with.
  • Embrace computation.
  • Embrace data.
  • Embrace bottom up.
  • Put the few proven, well established and most powerful abstractions to work.
  • Restrain your appetite for syntactic sugar.

The Code

/*
A Planckservice - smallest possible microservice.
Implemented using state machines with actions written in Oblectamenta Assembler.
*/

kind Event;                                    //You can now write Event MyEvent, and MyEvent is known to be a special 'thing' associated with Event
kind Guard;                                    // Guards for transitions, again you can now write Guard myguard;


kind OblectamentaMsgDefDirective;              // Is used in connection with the serialization of messages
kind OblectamentaMsgReadDirective;             // Is used in connection with the deserialization of messages
kind OblectamentaMessageModifier;              // Indicates flags modifying the standad behaviour of serialization/deserialization of messages

OblectamentaMessageTag i32;                    // Used when writing/reading message fields which contain 32 bit integers
OblectamentaMessageTag i64;                    // Used when writing/reading message fields which contain 64 bit integers
OblectamentaMessageTag f64;                    // Used when writing/reading message fields which contain 64 bit floats (IEE 754)
OblectamentaMessageTag sz;                     // ... zero terminated strings
OblectamentaMessageModifier all;               // This one let you iterate over all fields of a given name

OblectamentaMsgDefDirective write;             // Indicates the serialization of a message
OblectamentaMsgReadDirective read;             // ... deserialization ...
   
OblectamentaDataLabel msg_buffer, accounts, 
                      count, client_count;     // Data labels mark the location of data in the global data segment

Event evAddAccount, evReplyOK, 
      evReplyNOKTableFull, evPrintTable;       //A couple of events used in transitions of state machines 

val max_accounts = 32;                         // val introduces a value which is computed beforehand, i.e. during compilation
val entry_len = 8;
val offs_a = 0;
val offs_b = 4;
val max_client_requests = 12;


oblectamenta{ 
 global{
   data{                                       // here are the global data definitions
    count; 0;                                  // count is the name of the location which contains a 32 bit integer with value 0 (all bits are 0)
    accounts; for (e: 1 .. max_accounts){0;0;} // reserves max_accounts of two consecutive 32 bit integers initialized with 0
                                               // for (...) {...} is evaluated during AST expansion, i.e. is a program transformation (meta programming)

    msg_buffer; for(e : 1 .. 32) {e;}          // Reserve 32 consecutively stored 32 bit integers initialized to the values 1,2,3,...,32
    client_count;0;                            // 32 bit integer initialized with 0
   };
 };
};


             
sm{                                            // Our service is modeled as a state machine
    Service;                                   // Name of the state machine
    states{Initial;Ready;ProcessRequest;};     // Atomic states 
    Actions{                                   // Actions are triggered in transitions
        doAddAccount{ 
         oblectamenta{text{asm{                   // yeah, a lot of typing to indicate an assemble routine 
           OblectamentaCodeLabel lbl_table_full;  // We will do a goto and need a label (this is the declaration)
           lea(count);                            // The Oblectamenta VM implements a hybrid (virtual-)machine architecture, it has plenty of registers and also a compute stack.
                                                  // lea(count) puts the address of the object labeled count on the top of the compute stack, hence
                                                  // CS = |&count| (we use the C address of operator &) 
           ldsi32;                                // Dereference the pointer on the top of CS and push the i32 value on CS
                                                  // addr =TOP(CS); value = *(int32_t*)addr; CS = |value of count|
           ldi32(max_accounts);                   // max_accounts is a constant value, ldi32 pushes the max_accounts on CS
                                                  // CS = |value of count|max_accounts|

           blteq(lbl_table_full);                 // a = pop 32 bit value from CS
                                                  // b = pop 32 bit value from CS
                                                  // (CS empty now)
                                                  // if b <= a jump to lbl_table_full
                                   
           lea(count);ldsi32;ldi32(entry_len);muli32;sti32(R0); // R0 = count * entry_len;
           lea(count);ldsi32;ldi32(1);addi32;lea(count);stsi32; // count = count + 1
           lea(accounts);ldi64(R0);addi64;sti64(R1);            // R1 = &accounts + R0 = &accounts[count-1]
           msg{                                 // msg{...} is NOT a machine language instruction of any kind, msg is a directive which gets
                                                // translated into an Oblectamenta fragment which allows for a compact notation of message serialization/deserialization
                read;                           // Indicates that code for deserialization has to be generated
                msg_buffer;
                Account{                        // The first element of the message is an Account, this innocuous looking struct is translated in a lengthy code fragment
                                                // which handles the correct deserialization of a node with the name Account and a variable number of sub nodes.
                    a{                          // Account.a
                        i32;                    // Read a 32 bit signed integer, we denote its value with a
                        ldi64(offs_a);          // |
                        ldi64(R1);              // |
                        addi64;                 // |
                        stsi32;                 // ===> Accounts[count-1].a = a
                    };
                    b{                         // Account.b
                        i32;                   // Read a 32 bit signed integer, we denote its value with b
                        ldi64(offs_b);         // |
                        ldi64(R1);             // |
                        addi64;                // |
                        stsi32;                // ===> Accounts[count-1].b = b
                    };
                };
            };
            assert_empty_cs;                   // CS should be empty, if not this will trigger an exception which prints a stack trace and terminates the process 
            evReplyOK;halt;                    // Reply with the event evReplyOK and stop
            lbl_table_full;evReplyNOKTableFull; // We end up here if and only if the table is full, we reply by sending the event evReplyNOKTableFull
         };};};
        };
    };
    t{Initial;Ready;};                         // state machine transitions immediately to the state Ready
    t{Ready;Ready;evAddAccount;doAddAccount;}; // We loop on Ready, each time we receive an event evAddAccount we add an account (assuming the message's payload is in msg_buffer)
};

sm{
    Client;
    Actions{
      doRequest{
        oblectamenta{text{asm{
           msg{
            write;
            msg_buffer;
            Account{
             a{ldi32(10);lea(client_count);ldsi32;addi32;i32;};
             b{ldi32(100);lea(client_count);ldsi32;addi32;i32;};
            };
           };
           lea(client_count);ldsi32;ldi32(1);addi32;lea(client_count);stsi32; // client_count = client_count + 1
         evAddAccount;                            
        };};};
      };
    };
    states{Initial;Request;WaitForReply;Final;};
    t{Initial;Request;};
    t{Request;WaitForReply;doRequest;};
    t{WaitForReply;Request;evReplyOK;};
    t{WaitForReply;Final;evReplyNOKTableFull;};
};

sm{
    TablePrinter;
    states{Initial;Print;};
    Actions{
     doPrintTable{oblectamenta{text{asm{
        OblectamentaCodeLabel lbl_done, lbl_loop;
        lea(accounts); sti64(R0);      // R0 = address of accounts
        lea(count);ldsi32;sti32(R1);   // R1 = number of stored entries
        ldi32(entry_len); sti32(R2);   // R2 = size of enry in bytes
        ldi32(0); sti32(R3);           // R3 = 0
        lbl_loop;
        ldi32(R3);ldi32(R1);           // CS = ...|R3(i32)|R1(i32)|
        blteq(lbl_done);               // if R1 <= R2 (<=> #entries <= counter) then gotot lbl_done
        ldi64(R0);ldi64(R3);ldi64(R2);muli64;addi64; // CS = ...|Address of R3th entry|
        duptopi64;
        ldsi32;
        dbg_print_topi32;
        discardtopi32;
        ldi64(offs_b);
        addi64;
        ldsi32;
        dbg_print_topi32;
        discardtopi32;
        ldi32(R3);ldi32(1);addi32;sti32(R3); // R3 = R3 + 1
        buc(lbl_loop);
        lbl_done;
        assert_empty_cs;        
     };};};};
    };
    t{Initial;Print;evReplyOK;doPrintTable;};
    t{Print;Initial;};
};

Simulation{
    Start{Service;Client;TablePrinter;};
};

About

Smallest possible micro service - Combines compactness of portable machine code with the expressiveness of high level abstractions, and the power of mathematics.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors