Commit c9ae30d27e

Andrew Kelley <superjoe30@gmail.com>
2017-04-04 00:11:57
delete alloca builtin function
See #225 introduce os.EnvMap
1 parent c400cb4
doc/langref.md
@@ -295,19 +295,6 @@ has a terminating null byte.
 Built-in functions are prefixed with `@`. Remember that the `comptime` keyword on
 a parameter means that the parameter must be known at compile time.
 
-### @alloca(comptime T: type, count: usize) -> []T
-
-Allocates memory in the stack frame of the caller. This temporary space is
-automatically freed when the function that called alloca returns to its caller,
-just like other stack variables.
-
-When using this function to allocate memory, you should know the upper bound
-of `count`. Consider putting a constant array on the stack with the upper bound
-instead of using alloca. If you do use alloca it is to save a few bytes off
-the memory size given that you didn't actually hit your upper bound.
-
-The allocated memory contents are undefined.
-
 ### @typeOf(expression) -> type
 
 This function returns a compile-time constant, which is the type of the
src/all_types.hpp
@@ -1190,7 +1190,6 @@ enum BuiltinFnId {
     BuiltinFnIdTruncate,
     BuiltinFnIdIntType,
     BuiltinFnIdSetDebugSafety,
-    BuiltinFnIdAlloca,
     BuiltinFnIdTypeName,
     BuiltinFnIdIsInteger,
     BuiltinFnIdIsFloat,
@@ -1706,7 +1705,6 @@ enum IrInstructionId {
     IrInstructionIdTruncate,
     IrInstructionIdIntType,
     IrInstructionIdBoolNot,
-    IrInstructionIdAlloca,
     IrInstructionIdMemset,
     IrInstructionIdMemcpy,
     IrInstructionIdSlice,
@@ -2234,14 +2232,6 @@ struct IrInstructionBoolNot {
     IrInstruction *value;
 };
 
-struct IrInstructionAlloca {
-    IrInstruction base;
-
-    IrInstruction *type_value;
-    IrInstruction *count;
-    LLVMValueRef tmp_ptr;
-};
-
 struct IrInstructionMemset {
     IrInstruction base;
 
src/codegen.cpp
@@ -2190,26 +2190,6 @@ static LLVMValueRef ir_render_truncate(CodeGen *g, IrExecutable *executable, IrI
     }
 }
 
-static LLVMValueRef ir_render_alloca(CodeGen *g, IrExecutable *executable, IrInstructionAlloca *instruction) {
-    TypeTableEntry *slice_type = get_underlying_type(instruction->base.value.type);
-    TypeTableEntry *ptr_type = slice_type->data.structure.fields[slice_ptr_index].type_entry;
-    TypeTableEntry *child_type = ptr_type->data.pointer.child_type;
-    LLVMValueRef size_val = ir_llvm_value(g, instruction->count);
-    LLVMValueRef ptr_val = LLVMBuildArrayAlloca(g->builder, child_type->type_ref, size_val, "");
-
-    // TODO in debug mode, initialize all the bytes to 0xaa
-
-    // store the freshly allocated pointer in the slice
-    LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, slice_ptr_index, "");
-    LLVMBuildStore(g->builder, ptr_val, ptr_field_ptr);
-
-    // store the size in the len field
-    LLVMValueRef len_field_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, slice_len_index, "");
-    LLVMBuildStore(g->builder, size_val, len_field_ptr);
-
-    return instruction->tmp_ptr;
-}
-
 static LLVMValueRef ir_render_memset(CodeGen *g, IrExecutable *executable, IrInstructionMemset *instruction) {
     LLVMValueRef dest_ptr = ir_llvm_value(g, instruction->dest_ptr);
     LLVMValueRef char_val = ir_llvm_value(g, instruction->byte);
@@ -2548,7 +2528,7 @@ static LLVMValueRef ir_render_maybe_wrap(CodeGen *g, IrExecutable *executable, I
     assert(instruction->tmp_ptr);
 
     LLVMValueRef val_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, maybe_child_index, "");
-    assert(child_type == instruction->value->value.type);
+    // child_type and instruction->value->value.type may differ by constness
     gen_assign_raw(g, val_ptr, payload_val, child_type);
     LLVMValueRef maybe_ptr = LLVMBuildStructGEP(g->builder, instruction->tmp_ptr, maybe_null_index, "");
     LLVMBuildStore(g->builder, LLVMConstAllOnes(LLVMInt1Type()), maybe_ptr);
@@ -2796,8 +2776,6 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_truncate(g, executable, (IrInstructionTruncate *)instruction);
         case IrInstructionIdBoolNot:
             return ir_render_bool_not(g, executable, (IrInstructionBoolNot *)instruction);
-        case IrInstructionIdAlloca:
-            return ir_render_alloca(g, executable, (IrInstructionAlloca *)instruction);
         case IrInstructionIdMemset:
             return ir_render_memset(g, executable, (IrInstructionMemset *)instruction);
         case IrInstructionIdMemcpy:
@@ -3648,9 +3626,6 @@ static void do_code_gen(CodeGen *g) {
             } else if (instruction->id == IrInstructionIdCall) {
                 IrInstructionCall *call_instruction = (IrInstructionCall *)instruction;
                 slot = &call_instruction->tmp_ptr;
-            } else if (instruction->id == IrInstructionIdAlloca) {
-                IrInstructionAlloca *alloca_instruction = (IrInstructionAlloca *)instruction;
-                slot = &alloca_instruction->tmp_ptr;
             } else if (instruction->id == IrInstructionIdSlice) {
                 IrInstructionSlice *slice_instruction = (IrInstructionSlice *)instruction;
                 slot = &slice_instruction->tmp_ptr;
@@ -4326,7 +4301,6 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
     create_builtin_fn(g, BuiltinFnIdIntType, "intType", 2);
     create_builtin_fn(g, BuiltinFnIdSetDebugSafety, "setDebugSafety", 2);
-    create_builtin_fn(g, BuiltinFnIdAlloca, "alloca", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalAlign, "setGlobalAlign", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalSection, "setGlobalSection", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalLinkage, "setGlobalLinkage", 2);
src/ir.cpp
@@ -404,10 +404,6 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionBoolNot *) {
     return IrInstructionIdBoolNot;
 }
 
-static constexpr IrInstructionId ir_instruction_id(IrInstructionAlloca *) {
-    return IrInstructionIdAlloca;
-}
-
 static constexpr IrInstructionId ir_instruction_id(IrInstructionMemset *) {
     return IrInstructionIdMemset;
 }
@@ -1668,27 +1664,6 @@ static IrInstruction *ir_build_bool_not_from(IrBuilder *irb, IrInstruction *old_
     return new_instruction;
 }
 
-static IrInstruction *ir_build_alloca(IrBuilder *irb, Scope *scope, AstNode *source_node,
-    IrInstruction *type_value, IrInstruction *count)
-{
-    IrInstructionAlloca *instruction = ir_build_instruction<IrInstructionAlloca>(irb, scope, source_node);
-    instruction->type_value = type_value;
-    instruction->count = count;
-
-    ir_ref_instruction(type_value, irb->current_basic_block);
-    ir_ref_instruction(count, irb->current_basic_block);
-
-    return &instruction->base;
-}
-
-static IrInstruction *ir_build_alloca_from(IrBuilder *irb, IrInstruction *old_instruction,
-    IrInstruction *type_value, IrInstruction *count)
-{
-    IrInstruction *new_instruction = ir_build_alloca(irb, old_instruction->scope, old_instruction->source_node, type_value, count);
-    ir_link_new_instruction(new_instruction, old_instruction);
-    return new_instruction;
-}
-
 static IrInstruction *ir_build_memset(IrBuilder *irb, Scope *scope, AstNode *source_node,
     IrInstruction *dest_ptr, IrInstruction *byte, IrInstruction *count)
 {
@@ -2567,14 +2542,6 @@ static IrInstruction *ir_instruction_boolnot_get_dep(IrInstructionBoolNot *instr
     }
 }
 
-static IrInstruction *ir_instruction_alloca_get_dep(IrInstructionAlloca *instruction, size_t index) {
-    switch (index) {
-        case 0: return instruction->type_value;
-        case 1: return instruction->count;
-        default: return nullptr;
-    }
-}
-
 static IrInstruction *ir_instruction_memset_get_dep(IrInstructionMemset *instruction, size_t index) {
     switch (index) {
         case 0: return instruction->dest_ptr;
@@ -2938,8 +2905,6 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t
             return ir_instruction_inttype_get_dep((IrInstructionIntType *) instruction, index);
         case IrInstructionIdBoolNot:
             return ir_instruction_boolnot_get_dep((IrInstructionBoolNot *) instruction, index);
-        case IrInstructionIdAlloca:
-            return ir_instruction_alloca_get_dep((IrInstructionAlloca *) instruction, index);
         case IrInstructionIdMemset:
             return ir_instruction_memset_get_dep((IrInstructionMemset *) instruction, index);
         case IrInstructionIdMemcpy:
@@ -4100,20 +4065,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
 
                 return ir_build_int_type(irb, scope, node, arg0_value, arg1_value);
             }
-        case BuiltinFnIdAlloca:
-            {
-                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
-                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
-                if (arg0_value == irb->codegen->invalid_instruction)
-                    return arg0_value;
-
-                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
-                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
-                if (arg1_value == irb->codegen->invalid_instruction)
-                    return arg1_value;
-
-                return ir_build_alloca(irb, scope, node, arg0_value, arg1_value);
-            }
         case BuiltinFnIdMemcpy:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -11461,31 +11412,6 @@ static TypeTableEntry *ir_analyze_instruction_bool_not(IrAnalyze *ira, IrInstruc
     return bool_type;
 }
 
-static TypeTableEntry *ir_analyze_instruction_alloca(IrAnalyze *ira, IrInstructionAlloca *instruction) {
-    IrInstruction *type_value = instruction->type_value->other;
-    if (type_is_invalid(type_value->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    IrInstruction *count_value = instruction->count->other;
-    if (type_is_invalid(count_value->value.type))
-        return ira->codegen->builtin_types.entry_invalid;
-
-    TypeTableEntry *child_type = ir_resolve_type(ira, type_value);
-
-    if (type_requires_comptime(child_type)) {
-        ir_add_error(ira, type_value,
-            buf_sprintf("invalid alloca type '%s'", buf_ptr(&child_type->name)));
-        // TODO if this is a typedecl, add error note showing the declaration of the type decl
-        return ira->codegen->builtin_types.entry_invalid;
-    } else {
-        TypeTableEntry *slice_type = get_slice_type(ira->codegen, child_type, false);
-        IrInstruction *new_instruction = ir_build_alloca_from(&ira->new_irb, &instruction->base, type_value, count_value);
-        ir_add_alloca(ira, new_instruction, slice_type);
-        return slice_type;
-    }
-    zig_unreachable();
-}
-
 static TypeTableEntry *ir_analyze_instruction_memset(IrAnalyze *ira, IrInstructionMemset *instruction) {
     IrInstruction *dest_ptr = instruction->dest_ptr->other;
     if (type_is_invalid(dest_ptr->value.type))
@@ -12550,8 +12476,6 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_int_type(ira, (IrInstructionIntType *)instruction);
         case IrInstructionIdBoolNot:
             return ir_analyze_instruction_bool_not(ira, (IrInstructionBoolNot *)instruction);
-        case IrInstructionIdAlloca:
-            return ir_analyze_instruction_alloca(ira, (IrInstructionAlloca *)instruction);
         case IrInstructionIdMemset:
             return ir_analyze_instruction_memset(ira, (IrInstructionMemset *)instruction);
         case IrInstructionIdMemcpy:
@@ -12749,7 +12673,6 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdTruncate:
         case IrInstructionIdIntType:
         case IrInstructionIdBoolNot:
-        case IrInstructionIdAlloca:
         case IrInstructionIdSlice:
         case IrInstructionIdMemberCount:
         case IrInstructionIdAlignOf:
src/ir_print.cpp
@@ -601,14 +601,6 @@ static void ir_print_truncate(IrPrint *irp, IrInstructionTruncate *instruction)
     fprintf(irp->f, ")");
 }
 
-static void ir_print_alloca(IrPrint *irp, IrInstructionAlloca *instruction) {
-    fprintf(irp->f, "@alloca(");
-    ir_print_other_instruction(irp, instruction->type_value);
-    fprintf(irp->f, ", ");
-    ir_print_other_instruction(irp, instruction->count);
-    fprintf(irp->f, ")");
-}
-
 static void ir_print_int_type(IrPrint *irp, IrInstructionIntType *instruction) {
     fprintf(irp->f, "@intType(");
     ir_print_other_instruction(irp, instruction->is_signed);
@@ -1049,9 +1041,6 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdTruncate:
             ir_print_truncate(irp, (IrInstructionTruncate *)instruction);
             break;
-        case IrInstructionIdAlloca:
-            ir_print_alloca(irp, (IrInstructionAlloca *)instruction);
-            break;
         case IrInstructionIdIntType:
             ir_print_int_type(irp, (IrInstructionIntType *)instruction);
             break;
std/os/index.zig
@@ -21,6 +21,8 @@ const mem = @import("../mem.zig");
 const Allocator = mem.Allocator;
 
 const io = @import("../io.zig");
+const HashMap = @import("../hash_map.zig").HashMap;
+const cstr = @import("../cstr.zig");
 
 error Unexpected;
 error SysResources;
@@ -284,12 +286,12 @@ pub const ChildProcess = struct {
         Close,
     };
 
-    pub fn spawn(exe_path: []const u8, args: []const []const u8, env: []const EnvPair,
+    pub fn spawn(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap,
         stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
     {
         switch (@compileVar("os")) {
             Os.linux, Os.macosx, Os.ios, Os.darwin => {
-                return spawnPosix(exe_path, args, env, stdin, stdout, stderr, allocator);
+                return spawnPosix(exe_path, args, env_map, stdin, stdout, stderr, allocator);
             },
             else => @compileError("Unsupported OS"),
         }
@@ -351,7 +353,7 @@ pub const ChildProcess = struct {
         };
     }
 
-    fn spawnPosix(exe_path: []const u8, args: []const []const u8, env: []const EnvPair,
+    fn spawnPosix(exe_path: []const u8, args: []const []const u8, env_map: &const EnvMap,
         stdin: StdIo, stdout: StdIo, stderr: StdIo, allocator: &Allocator) -> %ChildProcess
     {
         // TODO issue #295
@@ -408,7 +410,7 @@ pub const ChildProcess = struct {
             setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %%
                 |err| forkChildErrReport(err_pipe[1], err);
 
-            const err = posix.getErrno(%return execve(exe_path, args, env, allocator));
+            const err = posix.getErrno(%return execve(exe_path, args, env_map, allocator));
             assert(err > 0);
             forkChildErrReport(err_pipe[1], switch (err) {
                 errno.EFAULT => unreachable,
@@ -473,7 +475,7 @@ pub const ChildProcess = struct {
 /// It must also convert to KEY=VALUE\0 format for environment variables, and include null
 /// pointers after the args and after the environment variables.
 /// Also make the first arg equal to path.
-fn execve(path: []const u8, argv: []const []const u8, envp: []const EnvPair, allocator: &Allocator) -> %usize {
+fn execve(path: []const u8, argv: []const []const u8, env_map: &const EnvMap, allocator: &Allocator) -> %usize {
     const path_buf = %return allocator.alloc(u8, path.len + 1);
     defer allocator.free(path_buf);
     @memcpy(&path_buf[0], &path[0], path.len);
@@ -505,39 +507,149 @@ fn execve(path: []const u8, argv: []const []const u8, envp: []const EnvPair, all
     }
     argv_buf[argv.len + 1] = null;
 
-    const envp_buf = %return allocator.alloc(?&const u8, envp.len + 1);
+    const envp_count = env_map.count();
+    const envp_buf = %return allocator.alloc(?&const u8, envp_count + 1);
     mem.set(?&const u8, envp_buf, null);
     defer {
         for (envp_buf) |env, i| {
-            const env_buf = if (const ptr ?= env) ptr[0...envp[i].key.len + envp[i].value.len + 2] else break;
+            const env_buf = if (const ptr ?= env) ptr[0...cstr.len(ptr)] else break;
             allocator.free(env_buf);
         }
         allocator.free(envp_buf);
     }
-    for (envp) |pair, i| {
-        const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
-        @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
-        env_buf[pair.key.len] = '=';
-        @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
-        env_buf[env_buf.len - 1] = 0;
-
-        envp_buf[i] = env_buf.ptr;
+    {
+        var it = env_map.iterator();
+        var i: usize = 0;
+        while (true; i += 1) {
+            const pair = it.next() ?? break;
+
+            const env_buf = %return allocator.alloc(u8, pair.key.len + pair.value.len + 2);
+            @memcpy(&env_buf[0], pair.key.ptr, pair.key.len);
+            env_buf[pair.key.len] = '=';
+            @memcpy(&env_buf[pair.key.len + 1], pair.value.ptr, pair.value.len);
+            env_buf[env_buf.len - 1] = 0;
+
+            envp_buf[i] = env_buf.ptr;
+        }
+        assert(i == envp_count);
     }
-    envp_buf[envp.len] = null;
+    envp_buf[envp_count] = null;
 
     return posix.execve(path_buf.ptr, argv_buf.ptr, envp_buf.ptr);
 }
 
-pub const EnvPair = struct {
-    key: []const u8,
-    value: []const u8,
+pub var environ_raw: []&u8 = undefined;
+
+pub const EnvMap = struct {
+    hash_map: EnvHashMap,
+
+    const EnvHashMap = HashMap([]const u8, []const u8, hash_slice_u8, eql_slice_u8);
+
+    pub fn init(allocator: &Allocator) -> EnvMap {
+        var self = EnvMap {
+            .hash_map = undefined,
+        };
+        self.hash_map.init(allocator);
+        return self;
+    }
+
+    pub fn deinit(self: &EnvMap) {
+        var it = self.hash_map.entryIterator();
+        while (true) {
+            const entry = it.next() ?? break; 
+            self.free(entry.key);
+            self.free(entry.value);
+        }
+
+        self.hash_map.deinit();
+    }
+
+    pub fn set(self: &EnvMap, key: []const u8, value: []const u8) -> %void {
+        if (const entry ?= self.hash_map.get(key)) {
+            const value_copy = %return self.copy(value);
+            %defer self.free(value_copy);
+            %return self.hash_map.put(key, value_copy);
+            self.free(entry.value);
+        } else {
+            const key_copy = %return self.copy(key);
+            %defer self.free(key_copy);
+            const value_copy = %return self.copy(value);
+            %defer self.free(value_copy);
+            %return self.hash_map.put(key_copy, value_copy);
+        }
+    }
+
+    pub fn delete(self: &EnvMap, key: []const u8) {
+        const entry = self.hash_map.remove(key) ?? return;
+        self.free(entry.key);
+        self.free(entry.value);
+    }
+
+    pub fn count(self: &const EnvMap) -> usize {
+        return self.hash_map.size;
+    }
+
+    pub fn iterator(self: &const EnvMap) -> EnvHashMap.Iterator {
+        return self.hash_map.entryIterator();
+    }
+
+    fn free(self: &EnvMap, value: []const u8) {
+        // remove the const
+        const mut_value = @ptrcast(&u8, value.ptr)[0...value.len];
+        self.hash_map.allocator.free(mut_value);
+    }
+
+    fn copy(self: &EnvMap, value: []const u8) -> %[]const u8 {
+        const result = %return self.hash_map.allocator.alloc(u8, value.len);
+        mem.copy(u8, result, value);
+        return result;
+    }
 };
-pub var environ: []const EnvPair = undefined;
+
+pub fn getEnvMap(allocator: &Allocator) -> %EnvMap {
+    var result = EnvMap.init(allocator);
+    %defer result.deinit();
+
+    for (environ_raw) |ptr| {
+        var line_i: usize = 0;
+        while (ptr[line_i] != 0 and ptr[line_i] != '='; line_i += 1) {}
+        const key = ptr[0...line_i];
+
+        var end_i: usize = line_i;
+        while (ptr[end_i] != 0; end_i += 1) {}
+        const value = ptr[line_i + 1...end_i];
+
+        %return result.set(key, value);
+    }
+    return result;
+}
 
 pub fn getEnv(key: []const u8) -> ?[]const u8 {
-    for (environ) |pair| {
-        if (mem.eql(u8, pair.key, key))
-            return pair.value;
+    for (environ_raw) |ptr| {
+        var line_i: usize = 0;
+        while (ptr[line_i] != 0 and ptr[line_i] != '='; line_i += 1) {}
+        const this_key = ptr[0...line_i];
+        if (!mem.eql(u8, key, this_key))
+            continue;
+
+        var end_i: usize = line_i;
+        while (ptr[end_i] != 0; end_i += 1) {}
+        const this_value = ptr[line_i + 1...end_i];
+
+        return this_value;
     }
     return null;
 }
+
+fn hash_slice_u8(k: []const u8) -> u32 {
+    // FNV 32-bit hash
+    var h: u32 = 2166136261;
+    for (k) |b| {
+        h = (h ^ b) *% 16777619;
+    }
+    return h;
+}
+
+fn eql_slice_u8(a: []const u8, b: []const u8) -> bool {
+    return mem.eql(u8, a, b);
+}
std/special/bootstrap.zig
@@ -9,8 +9,7 @@ const want_start_symbol = !want_main_symbol;
 
 const exit = std.os.posix.exit;
 
-var argc: usize = undefined;
-var argv: &&u8 = undefined;
+var argc_ptr: &usize = undefined;
 
 export nakedcc fn _start() -> noreturn {
     @setGlobalLinkage(_start, if (want_start_symbol) GlobalLinkage.Strong else GlobalLinkage.Internal);
@@ -20,21 +19,28 @@ export nakedcc fn _start() -> noreturn {
 
     switch (@compileVar("arch")) {
         Arch.x86_64 => {
-            argc = asm("mov %[argc], [rsp]": [argc] "=r" (-> usize));
-            argv = asm("lea %[argv], [rsp + 8h]": [argv] "=r" (-> &&u8));
+            argc_ptr = asm("lea %[argc], [rsp]": [argc] "=r" (-> &usize));
         },
         Arch.i386 => {
-            argc = asm("mov %[argc], [esp]": [argc] "=r" (-> usize));
-            argv = asm("lea %[argv], [esp + 4h]": [argv] "=r" (-> &&u8));
+            argc_ptr = asm("lea %[argc], [esp]": [argc] "=r" (-> &usize));
         },
         else => @compileError("unsupported arch"),
     }
     callMainAndExit()
 }
 
-fn callMain(envp: &?&u8) -> %void {
-    // TODO issue #225
-    const args = @alloca([]u8, argc);
+fn callMainAndExit() -> noreturn {
+    const argc = *argc_ptr;
+    const argv = @ptrcast(&&u8, &argc_ptr[1]);
+    const envp = @ptrcast(&?&u8, &argv[argc + 1]);
+    callMain(argc, argv, envp) %% exit(1);
+    exit(0);
+}
+
+var args_data: [32][]u8 = undefined;
+fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void {
+    // TODO create args API to make it work with > 32 args
+    const args = args_data[0...argc];
     for (args) |_, i| {
         const ptr = argv[i];
         args[i] = ptr[0...std.cstr.len(ptr)];
@@ -42,41 +48,17 @@ fn callMain(envp: &?&u8) -> %void {
 
     var env_count: usize = 0;
     while (envp[env_count] != null; env_count += 1) {}
-    // TODO issue #225
-    const environ = @alloca(std.os.EnvPair, env_count);
-    for (environ) |_, env_i| {
-        const ptr = ??envp[env_i];
-
-        var line_i: usize = 0;
-        while (ptr[line_i] != 0 and ptr[line_i] != '='; line_i += 1) {}
-
-        var end_i: usize = line_i;
-        while (ptr[end_i] != 0; end_i += 1) {}
-
-        environ[env_i] = std.os.EnvPair {
-            .key = ptr[0...line_i],
-            .value = ptr[line_i + 1...end_i],
-        };
-    }
-    std.os.environ = environ;
+    std.os.environ_raw = @ptrcast(&&u8, envp)[0...env_count];
 
     return root.main(args);
 }
 
-fn callMainAndExit() -> noreturn {
-    const envp = @ptrcast(&?&u8, &argv[argc + 1]);
-    callMain(envp) %% exit(1);
-    exit(0);
-}
-
 export fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 {
     @setGlobalLinkage(main, if (want_main_symbol) GlobalLinkage.Strong else GlobalLinkage.Internal);
     if (!want_main_symbol) {
         unreachable;
     }
 
-    argc = usize(c_argc);
-    argv = c_argv;
-    callMain(c_envp) %% return 1;
+    callMain(usize(c_argc), c_argv, c_envp) %% return 1;
     return 0;
 }
std/build.zig
@@ -40,6 +40,8 @@ pub const Builder = struct {
     }
 
     pub fn make(self: &Builder, cli_args: []const []const u8) -> %void {
+        var env_map = %return os.getEnvMap(self.allocator);
+
         var verbose = false;
         for (cli_args) |arg| {
             if (mem.eql(u8, arg, "--verbose")) {
@@ -93,7 +95,7 @@ pub const Builder = struct {
             }
 
             printInvocation(self.zig_exe, zig_args);
-            var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), os.environ,
+            var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), env_map,
                 StdIo.Ignore, StdIo.Inherit, StdIo.Inherit, self.allocator);
             const term = %return child.wait();
             switch (term) {
std/hash_map.zig
@@ -29,7 +29,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
         };
 
         pub const Iterator = struct {
-            hm: &Self,
+            hm: &const Self,
             // how many items have we returned
             count: usize,
             // iterator through the entry array
@@ -99,17 +99,21 @@ pub fn HashMap(comptime K: type, comptime V: type,
         }
 
         pub fn get(hm: &Self, key: K) -> ?&Entry {
+            if (hm.entries.len == 0) {
+                return null;
+            }
             return hm.internalGet(key);
         }
 
-        pub fn remove(hm: &Self, key: K) {
+        pub fn remove(hm: &Self, key: K) -> ?&Entry {
             hm.incrementModificationCount();
             const start_index = hm.keyToIndex(key);
             {var roll_over: usize = 0; while (roll_over <= hm.max_distance_from_start_index; roll_over += 1) {
                 const index = (start_index + roll_over) % hm.entries.len;
                 var entry = &hm.entries[index];
 
-                assert(entry.used); // key not found
+                if (!entry.used)
+                    return null;
 
                 if (!eql(entry.key, key)) continue;
 
@@ -119,7 +123,7 @@ pub fn HashMap(comptime K: type, comptime V: type,
                     if (!next_entry.used or next_entry.distance_from_start_index == 0) {
                         entry.used = false;
                         hm.size -= 1;
-                        return;
+                        return entry;
                     }
                     *entry = *next_entry;
                     entry.distance_from_start_index -= 1;
@@ -127,10 +131,10 @@ pub fn HashMap(comptime K: type, comptime V: type,
                 }
                 unreachable // shifting everything in the table
             }}
-            unreachable // key not found
+            return null;
         }
 
-        pub fn entryIterator(hm: &Self) -> Iterator {
+        pub fn entryIterator(hm: &const Self) -> Iterator {
             return Iterator {
                 .hm = hm,
                 .count = 0,
@@ -231,7 +235,8 @@ test "basicHashMapTest" {
     %%map.put(5, 55);
 
     assert((??map.get(2)).value == 22);
-    map.remove(2);
+    _ = map.remove(2);
+    assert(map.remove(2) == null);
     assert(if (const entry ?= map.get(2)) false else true);
 }
 
test/cases/const_slice_child.zig
@@ -1,4 +1,5 @@
-const assert = @import("std").debug.assert;
+const debug = @import("std").debug;
+const assert = debug.assert;
 
 var argv: &const &const u8 = undefined;
 
@@ -20,7 +21,7 @@ fn foo(args: [][]const u8) {
 }
 
 fn bar(argc: usize) {
-    const args = @alloca([]u8, argc);
+    const args = %%debug.global_allocator.alloc([]u8, argc);
     for (args) |_, i| {
         const ptr = argv[i];
         args[i] = ptr[0...strlen(ptr)];