06f1a7eb3ac7 — Leonard Ritter 10 months ago
uvm: fixed code generator for situations where nested labels would fail to propagate export scope
M include/uvm.h +4 -2
@@ 182,9 182,11 @@ typedef enum uvm_trace_bits_ {
 // flags for uvm_dump_disas
 typedef enum uvm_dump_bits_ {
     // don't dump block instructions, just data section
-    uvm_dump_no_instructions = 1 << 0,
+    uvmdf_no_instructions = 1 << 0,
     // don't dump block meta information
-    uvm_dump_no_block_meta_info = 1 << 1,
+    uvmdf_no_block_meta_info = 1 << 1,
+    // dump all referenced blocks, directly or indirectly
+    uvmdf_recursive = 1 << 2,
 } uvm_dump_bits_t;
 
 // initialize with 0 for default or another fixed address that is sure to

          
M lib/scopes/compiler/target/UVM/init.sc +90 -33
@@ 33,7 33,7 @@ struct UVMBlock
     # holds live assignments maintained
     context_scope = ILNullRef
     # local values to capture for subscope
-    scope : OrderedSet ILBlockRef
+    export_scope : OrderedSet ValueIndex
     # holds global variables, heap and stack; routed through every block to
         emulate mutation.
     global_scope = ILNullRef

          
@@ 52,18 52,6 @@ struct UVMFunction
     stack : Array UVMBlock
     func : Value = null
 
-    #value_cache : (Map ValueIndex Id)
-    #init_block : UVMBlock
-    #block : UVMBlock
-    #active_label : Id = 0
-    #active_break_label : Id = 0
-
-    inline push (self vm ...)
-        'append self.stack
-            UVMBlock
-                builder = ILBuilder vm
-                ...
-
     inline top (self)
         'last self.stack
 

          
@@ 77,6 65,7 @@ struct UVMFunction
         level := 'top self
         if (level.mem == ILNullRef)
             using ('builder self)
+            #uvm.comment (text = "get mem")
             level.mem = uvm.get (value = level.global_scope) (index = GSCOPE_MEM)
             level.exported_mem = level.mem
         level.mem

          
@@ 122,15 111,70 @@ struct UVMFunction
             arg := copy ('resolve self v i-1)
             # must be exported by the above level first
             parent := self.stack @ i-1
-            'insert parent.scope arg
-            idx := ('indexof parent.scope arg) as i32
+            'insert parent.export_scope v
+            idx := ('indexof parent.export_scope v) as i32
             level := self.stack @ i
             arg := do
                 using level.builder
+                #uvm.comment
+                    text = "import-scope arg"
                 uvm.get (value = level.context_scope) (index = idx)
             'set level.values v arg
             arg
 
+    fn build-scope (self)
+        level := 'top self
+        valcount := (countof level.export_scope) as i32
+        do
+            using ('builder self)
+            local context = uvm.alloc (size = 0) (count = valcount)
+            #uvm.comment
+                text = "export-scope {"
+            for i val in (enumerate level.export_scope)
+                val := try! ('get level.values val)
+                context = uvm.set
+                    value = context
+                    element = val
+                    index = i
+            #uvm.comment
+                text = "} export-scope"
+            context
+
+    fn transport-scope (self)
+        # export all variables from the parent export scope, if any
+        count := countof self.stack
+        if (count <= 1)
+            return;
+        idx := count - 1
+        parent_idx := idx - 1
+        parent := self.stack @ parent_idx
+        using ('builder self)
+        #uvm.comment
+            text = "transport-scope {"
+        level := 'top self
+        # inherit 1:1 mapping of parent export scope so the routing to
+            later blocks remains as expected.
+        assert (empty? level.export_scope)
+        for i v in (enumerate parent.export_scope)
+            arg := do
+                uvm.get (value = level.context_scope) (index = i)
+            v := copy v
+            'set level.values v arg
+            'insert level.export_scope v
+        #uvm.comment
+            text = "} transport-scope"
+        ;
+
+    inline pop (self)
+        'pop self.stack
+        ;
+
+    inline push (self vm ...)
+        'append self.stack
+            UVMBlock
+                builder = ILBuilder vm
+                ...
+
 ################################################################################
 
 struct UVMGenerator

          
@@ 185,8 229,11 @@ struct UVMGenerator
         using ('builder ctx)
         level := 'top ctx
         scope := (uvm.scope)
+        #uvm.comment (text = "get branch gscope")
         level.global_scope = uvm.get (value = scope) (index = 0)
+        #uvm.comment (text = "get branch lscope")
         level.context_scope = uvm.get (value = scope) (index = 1)
+        'transport-scope ctx
         'translate-block self ctx block
         'finalize-blocks self ctx startlevel
 

          
@@ 806,7 853,9 @@ struct UVMGenerator
                 mem := 'mem ctx
                 result := uvm.ccall (mem = mem) (target = func) (value = args)
                 if (returning? RT)
+                    #uvm.comment (text = "get mem")
                     mem = uvm.get (value = result) (index = 0)
+                    #uvm.comment (text = "get result")
                     uvm.get (value = result) (index = 1)
                 else
                     uvm.unreachable;

          
@@ 863,6 912,7 @@ struct UVMGenerator
                 level.mem = ILNullRef
                 for i argT in (enumerate (argument-type-elements rtype))
                     #valtype := 'translate-type self argT
+                    #uvm.comment (text = "get return value")
                     'map@ ctx instr i
                         uvm.get (value = retval) (index = i + 1)
         case ValueKind.CondBr

          
@@ 880,7 930,7 @@ struct UVMGenerator
             using ('builder ctx)
             # transfer heap and scope
             level := 'top ctx
-            context := 'build-scope self ctx
+            context := 'build-scope ctx
             scope := uvm.alloc (size = 0) (count = 2)
             scope := uvm.set (value = scope) (element = 'synced-gscope ctx) (index = 0)
             scope := uvm.set (value = scope) (element = context) (index = 1)

          
@@ 914,7 964,7 @@ struct UVMGenerator
             using ('builder ctx)
             # transfer heap and scope
             level := 'top ctx
-            context := 'build-scope self ctx
+            context := 'build-scope ctx
             scope := uvm.alloc (size = 0) (count = 2)
             scope := uvm.set (value = scope) (element = 'synced-gscope ctx) (index = 0)
             scope := uvm.set (value = scope) (element = context) (index = 1)

          
@@ 934,6 984,9 @@ struct UVMGenerator
                 when the block is finalized, the label contents need to
                 be filled in, with a final goto to our new block; all
                 this info can be retrieved while unpopping the stack.
+            #do
+                using ('builder ctx)
+                uvm.comment (text = "--- label body starts here")
             rtype := 'qualified-typeof instr
             level := 'top ctx
             assert (not level.label)

          
@@ 945,8 998,11 @@ struct UVMGenerator
             env := (uvm.scope)
             #result := get (value = env) (index = 1)
             level := 'top ctx
+            #uvm.comment (text = "get label gscope")
             level.global_scope = uvm.get (value = env) (index = 0)
+            #uvm.comment (text = "get label lscope")
             level.context_scope = uvm.get (value = env) (index = 1)
+            'transport-scope ctx
             if (returning? rtype)
 
                 #'set ctx.value_cache (ValueIndex instr -1) id

          
@@ 961,6 1017,7 @@ struct UVMGenerator
                 for i argT in (enumerate (argument-type-elements rtype))
                     #valname := 'dedup-valuename ctx (.. vn "_i")
                     #valtype := 'translate-type self argT
+                    #uvm.comment (text = "get merge return value")
                     'map@ ctx instr i
                         uvm.get (value = env) (index = (i + 2))
             #else

          
@@ 980,12 1037,15 @@ struct UVMGenerator
                 this-block := (uvm.block)
                 'map@ ctx instr -1 this-block
                 level := 'top ctx
+                #uvm.comment (text = "get loop label gscope")
                 level.global_scope = uvm.get
                     value = scope
                     index = 0
+                #uvm.comment (text = "get loop label lscope")
                 level.context_scope = uvm.get
                     value = scope
                     index = 1
+                'transport-scope ctx
                 # repeat needs to route the context back into the loop, so
                     we bind it here verbatim.
                 'map@ ctx instr -2 level.context_scope

          
@@ 993,6 1053,7 @@ struct UVMGenerator
                 params := 'typed-attribute@ instr 0 0
                 paramtypes := 'qualified-typeof params
                 for i argT in (enumerate (argument-type-elements paramtypes))
+                    #uvm.comment (text = "get loop label argument")
                     val := uvm.get
                         value = scope
                         index = i + 2

          
@@ 1005,7 1066,7 @@ struct UVMGenerator
             do
                 level := 'top ctx
                 using ('builder ctx)
-                context := 'build-scope self ctx
+                context := 'build-scope ctx
                 # build initial scope
                 argcount := 'typed-attribute-count instr 1
                 local initvals = uvm.alloc (size = 0) (count = argcount + 2)

          
@@ 1210,6 1271,8 @@ struct UVMGenerator
             dest := try (copy ('get ctx.labels value))
             else
                 error "cannot jump to label because it has not been translated yet"
+            #'transport-scope ctx
+            # finish label body
             body := 'typed-body value
             'translate-block self ctx body
         func := 'toblock ('builder ctx)

          
@@ 1217,7 1280,7 @@ struct UVMGenerator
         try ('unwrap level.alias)
         then (value)
             'set ctx.labels (copy value) func
-        'pop ctx.stack
+        'pop ctx
         func
 
     inline finalize-all-but-current-block (self ctx startlevel)

          
@@ 1231,19 1294,6 @@ struct UVMGenerator
     inline translate-value (self ctx value)
         'translate self ctx (ValueIndex value)
 
-    inline build-scope (self ctx)
-        level := 'top ctx
-        valcount := (countof level.scope) as i32
-        do
-            using ('builder ctx)
-            local context = uvm.alloc (size = 0) (count = valcount)
-            for i val in (enumerate level.scope)
-                context = uvm.set
-                    value = context
-                    element = val
-                    index = i
-            context
-
     fn translate-ffi-type (self T)
         returning uvm_cell_t
         raising Error

          
@@ 1420,15 1470,18 @@ struct UVMGenerator
                 thisfunc := (uvm.block)
                 'map subctx value thisfunc
                 # has format {heap, empty}; second slot can be used for local state
+                #uvm.comment (text = "get function gscope")
                 global_scope := uvm.get (value = scope) (index = 0)
                 #heap := uvm.call (block = self.builtin_heap) (scope = 'cell self.vm)
                 #global_scope := uvm.alloc (size = 0) (count = 2)
                 #global_scope := uvm.set (value = global_scope) (element = heap) (index = 0)
                 # patched later
+                #uvm.comment (text = "get functable")
                 functable := uvm.get (value = global_scope) (index = GSCOPE_FUNCTABLE)
                 'map@ subctx value -1 functable
                 level.global_scope = global_scope
                 for i param in (enumerate ('typed-attributes value 0))
+                    #uvm.comment (text = "get function argument")
                     instr := uvm.get
                         value = scope
                         index = i + 1

          
@@ 1649,8 1702,11 @@ struct UVMGenerator
             'indexof self.functable value
         then (idx)
             # patch it in
+            assert (ctx.func != null)
             functableid := try! ('resolve ctx (ValueIndex (copy ctx.func) -1) i)
             using ('builder ctx)
+            #uvm.comment
+                text = "get functable func"
             f := uvm.get (value = functableid) (index = idx)
             'map ctx value f
             return f

          
@@ 1709,7 1765,6 @@ struct UVMGenerator
         level := 'top subctx
         scope := (uvm.scope)
         scope := fold (scope) for f in self.constructors
-            print ('typeof f)
             func := 'translate-constant self (copy f) 0
             uvm.call (block = func) (scope = scope)
         uvm.return (value = scope)

          
@@ 1728,6 1783,8 @@ struct UVMGenerator
 
         if (self.flags & compile-flag-dump-disassembly)
             uvm_dump_disas self.vm func 0
+        elseif (self.flags & compile-flag-dump-module)
+            uvm_dump_disas self.vm func uvmdf_recursive
 
         entry := uvm_store self.vm func
         ftable := do

          
M src/uvm/gen_uvm.sc +6 -3
@@ 19,7 19,10 @@ UVM_OPS := sugar-quote
     get value:ref index:ref
     # set value at index to element and drop previous element
     set value:ref element:ref index:ref
-    countof value:ref
+    # the specializer must be free to append to a cell without affecting
+        subsequent computations. dynamic sizes and counts must be passed
+        explicitly.
+    #countof value:ref
 
     # hylomorphic pointer operations
       ------------------------------

          
@@ 36,8 39,8 @@ UVM_OPS := sugar-quote
     slice value:ref offset:ref size:ref
     # set value at index to element
     replace value:ref offset:ref element:ref
-    # return size of data
-    sizeof value:ref
+    # removed; see rationale for (the also removed) countof above
+    #sizeof value:ref
 
     # scalar bitwise unary operations
       -------------------------------

          
M src/uvm/uvm.cpp +69 -42
@@ 77,6 77,10 @@ receives parameters, is evaluated and pr
 
 namespace uvm {
 
+// todo: get this value from the OS
+static const size_t uvm_pagesize = sysconf(_SC_PAGE_SIZE);
+#define UVM_PAGESIZE ((size_t)uvm_pagesize)
+
 #define UVM_VERSION_STRING_BASE "UVM-0.1"
 #if UVM_REBUILD_CACHE_ON_COMPILE
 #define UVM_VERSION_STRING UVM_VERSION_STRING_BASE "-" __DATE__ "-" __TIME__

          
@@ 499,7 503,6 @@ struct CHeap {
 
 struct MemWriteTracker {
     int uffd;
-    long pagesize;
     volatile int stop_pagefault_handler;
     pthread_t uffd_thread;
 

          
@@ 596,7 599,7 @@ static void *handler(void *arg)
                 // write protection
                 struct uffdio_writeprotect uffdio_wp;
                 uffdio_wp.range.start = msg.arg.pagefault.address;
-                uffdio_wp.range.len = p.pagesize;
+                uffdio_wp.range.len = UVM_PAGESIZE;
                 uffdio_wp.mode = 0;
                 if (ioctl(p.uffd, UFFDIO_WRITEPROTECT, &uffdio_wp) == -1) {
                     perror("thread/ioctl/uffdio_writeprotect");

          
@@ 610,7 613,7 @@ static void *handler(void *arg)
                 // zeropage
                 struct uffdio_zeropage zp;
                 zp.range.start = msg.arg.pagefault.address;
-                zp.range.len = p.pagesize;
+                zp.range.len = UVM_PAGESIZE;
                 zp.mode = 0;
                 if (ioctl(p.uffd, UFFDIO_ZEROPAGE, &zp) == -1) {
                     perror("ioctl/zeropage");

          
@@ 691,7 694,6 @@ void uvm_setup_userfault_handler () {
     mwt.stop_pagefault_handler = 0;
 
     mwt.uffd = uffd;
-    mwt.pagesize = sysconf(_SC_PAGE_SIZE);
 
     pthread_create(&mwt.uffd_thread, NULL, handler, &mwt);
 

          
@@ 798,6 800,10 @@ UVM::UVM() {
         err = mdb_env_create(&env);
         assert(!err);
 
+        // todo: increase map size on demand
+        err = mdb_env_set_mapsize(env, UVM_PAGESIZE * 2560);
+        assert(!err);
+
         err = mdb_env_open(env, dbpath, MDB_NOSUBDIR, 0666);
         assert(!err);
 

          
@@ 2116,7 2122,7 @@ void uvm_dump_disas_instr(UVM &vm, Cell 
 #undef Fu32
     default: w += fprintf(stderr, "?"); break;
     }
-    if (!(flags & uvm_dump_no_block_meta_info)) {
+    if (!(flags & uvmdf_no_block_meta_info)) {
         for (; w < 40; ++w) {
             fprintf(stderr, " ");
         }

          
@@ 2131,42 2137,58 @@ void uvm_dump_disas_instr(UVM &vm, Cell 
 }
 
 void uvm_dump_disas(UVM &vm, Cell &cell, uint32_t flags) {
-    if (!(flags & uvm_dump_no_instructions)) {
-        fprintf(stderr, "  # block ");
-        uvm_print_uref(stderr, uvm_cell_uref(vm, cell));
-        auto err = uvm_check_valid_block(vm, cell);
-        if (err != Validated_LooksFine) {
-            fprintf(stderr, " !%s", uvm_block_validation_name(err));
-        }
-        fprintf(stderr, "\n");
-        size_t count = cell.size() / sizeof(Instruction);
-        for (size_t i = 0; i < count; ++i) {
-            uvm_dump_disas_instr(vm, cell, i, flags);
-        }
-    } // if (!(flags & DumpFlags_NoInstructions))
-    if (cell.count()) {
-        fprintf(stderr, "  # data\n");
-        for (size_t i = 0; i < cell.count(); ++i) {
-            fprintf(stderr, "    @%zu = ", i);
-            auto subcell = cell.cells()[i];
-            if (!subcell) {
-                fprintf(stderr, "undef ");
-            }
-            if (!uvm_try_unthunk(vm, *subcell)) {
-                uvm_print_uref(stderr, uvm_cell_uref(vm, *subcell));
-                fprintf(stderr, " ");
-                fprintf(stderr, "<missing> ");
-            } else if (uvm_is_valid_block(vm, *subcell)) {
-                fprintf(stderr, "block ");
-                uvm_print_uref(stderr, uvm_cell_uref(vm, *subcell));
-            } else {
-                uvm_print_uref(stderr, uvm_cell_uref(vm, *subcell));
-                fprintf(stderr, " ");
-                uvm_dump(vm, *subcell);
+    std::vector<Cell *> todo;
+    std::unordered_set<uvm_uref, uvm_uref_hash> seen;
+    todo.push_back(&cell);
+    seen.insert(uvm_cell_uref(vm, cell));
+    while (!todo.empty()) {
+        auto &cell = *todo.back();
+        todo.pop_back();
+        if (!(flags & uvmdf_no_instructions)) {
+            fprintf(stderr, "  # block ");
+            uvm_print_uref(stderr, uvm_cell_uref(vm, cell));
+            auto err = uvm_check_valid_block(vm, cell);
+            if (err != Validated_LooksFine) {
+                fprintf(stderr, " !%s", uvm_block_validation_name(err));
             }
             fprintf(stderr, "\n");
+            size_t count = cell.size() / sizeof(Instruction);
+            for (size_t i = 0; i < count; ++i) {
+                uvm_dump_disas_instr(vm, cell, i, flags);
+            }
+        } // if (!(flags & DumpFlags_NoInstructions))
+        if (cell.count()) {
+            fprintf(stderr, "  # data\n");
+            for (size_t i = 0; i < cell.count(); ++i) {
+                fprintf(stderr, "    @%zu = ", i);
+                auto subcell = cell.cells()[i];
+                if (!subcell) {
+                    fprintf(stderr, "undef ");
+                }
+                if (!uvm_try_unthunk(vm, *subcell)) {
+                    uvm_print_uref(stderr, uvm_cell_uref(vm, *subcell));
+                    fprintf(stderr, " ");
+                    fprintf(stderr, "<missing> ");
+                } else if (uvm_is_valid_block(vm, *subcell)) {
+                    fprintf(stderr, "block ");
+                    auto subcell_uref = uvm_cell_uref(vm, *subcell);
+                    uvm_print_uref(stderr, subcell_uref);
+                    if (flags & uvmdf_recursive) {
+                        if (!seen.count(subcell_uref)) {
+                            todo.push_back(subcell);
+                            seen.insert(subcell_uref);
+                        }
+                    }
+                } else {
+                    uvm_print_uref(stderr, uvm_cell_uref(vm, *subcell));
+                    fprintf(stderr, " ");
+                    uvm_dump(vm, *subcell);
+                }
+                fprintf(stderr, "\n");
+            }
         }
     }
+    fprintf(stderr, "# %zu block(s) total\n", seen.size());
 }
 
 // we need to simulate accessing a contained pointer; for this, we

          
@@ 2225,7 2247,7 @@ void uvm_dump_stack_entry(UVM &vm, size_
     if (true) {
         if (block) {
             fprintf(stderr, "\n");
-            uvm_dump_disas(vm, *cell, uvm_dump_no_instructions);
+            uvm_dump_disas(vm, *cell, uvmdf_no_instructions);
         } else {
             uvm_dump(vm, *cell);
             fprintf(stderr, "\n");

          
@@ 2263,13 2285,13 @@ void uvm_dump_stack(UVM &vm) {
         uvm_dump_stack_entry(vm, start, start - 1, seen);
         Cell *block = vm.stack[start - 2].cell;
         for (size_t i = start; i < end; ++i) {
-            uvm_dump_disas_instr(vm, *block, i - start, uvm_dump_no_block_meta_info);
+            uvm_dump_disas_instr(vm, *block, i - start, uvmdf_no_block_meta_info);
             fprintf(stderr, "%%%zu -> ", i - start);
             uvm_dump_stack_entry(vm, start, i, seen);
         }
         auto numinstr = block->size() / sizeof(Instruction);
         if ((end - start) < numinstr)
-            uvm_dump_disas_instr(vm, *block, end - start, uvm_dump_no_block_meta_info);
+            uvm_dump_disas_instr(vm, *block, end - start, uvmdf_no_block_meta_info);
     }
     fprintf(stderr, "\n");
 }

          
@@ 2316,7 2338,7 @@ void uvm_dump_trace(UVM &vm) {
     }
     auto numinstr = block->size() / sizeof(Instruction);
     if ((end - start) < numinstr)
-        uvm_dump_disas_instr(vm, *block, end - start, uvm_dump_no_block_meta_info);
+        uvm_dump_disas_instr(vm, *block, end - start, 0/*uvm_dump_no_block_meta_info*/);
 }
 
 inline Instruction *uvm_stack_get_instruction(UVM &vm, uvm_ref ip) {

          
@@ 2477,6 2499,12 @@ inline void uvm_stack_cleanup_scope(UVM 
 
 inline void uvm_stack_goto(UVM &vm, Cell &block, Cell &scope, uvm_instruction_kind_t kind) {
     uvm_unthunk(vm, block);
+    if (!uvm_is_valid_block(vm, block)) {
+        uvm_dump_disas(vm, block);
+        assert(false && "invalid block");
+        fprintf(stderr, "UVM: jumping to invalid block");
+        exit(1);
+    }
     uvm_stack_cleanup_scope(vm);
     uvm_stack_set(vm, vm.stack_start - 2, &block);
     uvm_stack_set(vm, vm.stack_start - 1, &scope);

          
@@ 2612,7 2640,6 @@ FFITypeCacheEntry &uvm_translate_ffi_typ
 // for the case that end < begin,
 // partbegin..begin covers the same partially used range as end..partend
 inline void usedpages(uintptr_t &partbegin, uintptr_t &begin, uintptr_t &end, uintptr_t &partend) {
-#define UVM_PAGESIZE 4096ull
     // round down
     partbegin = begin & ~(UVM_PAGESIZE-1);
     // round up

          
M testing/test_uvm.sc +66 -2
@@ 83,6 83,8 @@ uvm-test-fib
 inline uvm-test-with-args (f ...)
     f := static-typify f
 
+    f;
+
     tflags cflags... := do
         static-fold (tflags cflags... = 0) for arg in (va-each ...)
             static-if (arg == 'execution)

          
@@ 122,6 124,46 @@ fn ()
     # variadic c call to global
     fprintf stderr "hello world %i %f\n" 1 pi
 
+@@ uvm-test #'execution #'memory #'disassembly
+fn ()
+    using import C.stdio
+    local str := "test\n"
+    if (unconst true)
+        if (unconst true)
+            fprintf stderr str
+            ;
+    if (unconst true)
+        fprintf stderr str
+        ;
+    ;
+
+@@ uvm-test #'execution #'memory #'disassembly
+fn ()
+    using import C.stdio
+    inline fwrite (data size)
+        fwrite (data as rawstring) 1 size stderr
+    fn unconst (x) x
+
+    str := "test\n"
+    size := 5:usize
+    loop (i = 0:usize)
+        if (< i size)
+            if (unconst true)
+                if (unconst true)
+                    c := str @ i
+                    fwrite &c 1
+            repeat (+ i 1)
+        else
+            break;
+    ;
+
+@@ uvm-test #'execution #'memory #'disassembly
+fn ()
+    using import print
+    # using the new print interface
+    print (/string "test")
+    ;
+
 @@ uvm-test #'execution #'memory
 fn ()
     fn unconst (x) x

          
@@ 131,8 173,7 @@ fn ()
     p := malloc-array i32 (unconst 256)
     free p
 
-#print "so far so good"
-@@ uvm-test 'memory #'execution #'memory 'disassembly
+@@ uvm-test #'memory #'execution #'memory 'disassembly
 fn ()
     # barebone dynamic array example
     fn unconst (x) x

          
@@ 176,6 217,29 @@ fn ()
     release self
     ;
 
+@@ uvm-test #'memory #'execution #'disassembly
+fn ()
+    # complex dynamic array example with resizing
+    local arr : (Array i32)
+    for i in (range 100)
+        'append arr i
+    for i in arr
+        printf "%i " i
+    printf "\n"
+    ;
+
+@@ uvm-test #'disassembly #'memory #'execution #
+fn ()
+    # complex dynamic array example with our cool printer
+    using import print
+    local arr : (Array i32)
+    for i in (range 100)
+        'append arr i
+    for i in arr
+        print i /..
+    print ""
+    ;
+
 ################################################################################
 
 #do