zetzit / zz

πŸΊπŸ™ ZetZ a zymbolic verifier and tranzpiler to bare metal C
MIT License
1.6k stars 52 forks source link

Generics #53

Closed richardanaya closed 4 years ago

richardanaya commented 4 years ago

Vectors of structs seem like such a common paradigm. To elaborate on maybe an idea. Imagine we have some struct Foo.

struct Foo {
   int a;
}

static Vec<Foo> all_foo = [ null; 100 ]

fn main() -> int {

  for (int mut i = 0; i < num::len() ; i++) {
     printf("%d",all_foo[i].a); 
  }
  return 0;
}

I'd be curious how this is done today :)

aep commented 4 years ago

maybe this is a bad example, but whats the vector for? You can just use an array.

aep commented 4 years ago
using <stdio.h>::{printf};

struct Foo {
    int a;
}

static Foo all_foo[100] = {0};

fn main() -> int {
    for (usize mut i = 0; i < static(len(all_foo)) ; i++) {
        printf("%d",all_foo[i].a); 
    }
    return 0;
}
aep@horstbook3000: ~/kram/bla zz run         
finished [Exe] bla                                                                                                                                                       
running "/Users/aep/kram/bla/target/test/bin/bla"

0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000% 
richardanaya commented 4 years ago

Ahh, sorry, here's a better example. In web assembly, we are relying upon external javascript calling exported functions from our code. Imagine I had some game.

extern rand() -> float;

struct Monster {
   int x;
   int y;
}

static Vec<Monster> all_monsters = [ null; 100 ]

export fn run_game_loop() {
  // note that len() doesn't return capacity, it returns how many entities have been pushed
  for (int mut i = 0; i < all_monsters::len() ; i++) {
     // render monster
  }
}

export fn key_pressed() {
    new m = Monster {  
           x: (int)(rand()*100),
           y: (int)(rand()*100)
    };
    all_monsters::push(m);
}
aep commented 4 years ago

ah i see.

so first of all zz doesnt have templates. https://github.com/zetzit/zz#metaprogramming-or-templates-tail-variants

"template functions blow up code size and are difficult to export as plain C types"

in your case, you're making an array with a free marker. its a standard C construct. the Vec here is just adding high level sugar around that.

struct Monster {
   bool used;
   int x;
   int y;
}

struct MonsterList+ {
   Monster m[];
}

fn push (MonsterList+t mut * self )
{
  // find free
}

static MonsterList+100 all_monsters  = {0};

removing the boilerplate should be fairly simple with the new proc macros, but i wont put that into std, due to the code size blowup problem. As soon as we have package repo, everyone is free to create such a macro of course.

for the use case you have, a more convenient option is a pool

struct Monster {
   int x;
   int y;
}

static pool::Pool+100 all_monsters  = {0};

export fn key_pressed() {
    Monster mut * m = all_monsters.alloc(sizeof(Monster));
    *m = Monster{  
           x: (int)(rand()*100),
           y: (int)(rand()*100)
    };
}

an in-between solution would be to use polymorphism, which is free in zz, while being very limited. you can construct something like a Vec generic with macros and polymorphism, but getting used to not using generic types is actually a good thing

aep commented 4 years ago

i also realized people coming from rust might really trip over how stack ownership works in ZZ. you wrote:

    new m = Monster {  
           x: (int)(rand()*100),
           y: (int)(rand()*100)
    };
    all_monsters::push(m);

but thats a copy, not a move. you cannot allocate stack and move it. neither can rust, it just fakes it in an ugly way.

ZZ really is fundamentally C. You need to allocate memory at the same point as its lifetime. i.e. what you want is m to have the lifetime of all_monsters, so you must allocate it from memory owned by all_monsters

aep commented 4 years ago

btw i just added block iterators to pool. which may be useful to your case.

here's a minimal http server using pool with comments.

let me know if this is understandable or if we need more docs or more syntactic sugar

using <stdio.h>::{printf};
using <assert.h>::{assert};

using string;
using address;
using err;
using tcp_server;
using tcp;
using io;
using io::unix;
using net;
using pool;

export fn main() -> int {

    // make a pool of 1000 bytes for allocating tcp::Socket
    new+1000 p   = pool::make(sizeof(tcp::Socket));

    new+1000 e   = err::make();
    new+10 async = unix::make();
    new srv_addr = address::from_cstr("0.0.0.0:8083");
    new server   = net::tcp_server(net::os(), &e, &srv_addr, &async);
    e.abort();

    for (;;) {

        // allocate a new socket
        let client = (tcp::Socket mut *)p.alloc();
        err::assert_safe(client);

        if server.accept(&e, client) == io::Result::Ready {
            e.abort();
            new+100 saddr = string::make();
            client->remote_addr.to_string(&saddr);
            printf("accepted new client %x from %s\n", client, saddr.cstr());
        } else {

            // we didnt acutally need that allocation
            p.free(client);

        }
        e.abort();

        // iterate over all allocations
        p.each(process_client, &p);

        io::wait(&async, &e);
        e.abort();
    }

    server.close();
    return 0;
}

// for each currently allocated item in the pool (thats a socket)
fn process_client(pool::Pool mut *p, void mut *item, void mut *user)
    where pool::member(item, p)
{
    let client  = (tcp::Socket mut *)item;
    new+1000 e  = err::make();
    new+100 sbuf = string::make();

    if client->recv(&e, &sbuf) == io::Result::Ready {
        e.abort();
        printf("%s\n", sbuf.cstr());

        new+1000 msg = string::make();
        msg.append_cstr("HTTP/1.1 200 OK\n");
        msg.append_cstr("Connection: close\n");
        msg.append_cstr("\n");
        msg.append_cstr("ok cool\n");
        client->send(&e, &msg);
        client->close();

        // note that its safe to manipulate the pool from the iterator
        p->free(item);
    }
    e.abort();
}
aep commented 4 years ago

generics are now supported via sized void. See https://github.com/zetzit/zz/blob/master/modules/map/src/main.zz