diff --git a/src/all_types.hpp b/src/all_types.hpp index e682255602..5d8a823e75 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -520,7 +520,6 @@ struct AstNodeUnwrapErrorExpr { enum CastOp { CastOpNoCast, // signifies the function call expression is not a cast CastOpNoop, // fn call expr is a cast, but does nothing - CastOpErrToInt, CastOpIntToFloat, CastOpFloatToInt, CastOpBoolToInt, @@ -1223,6 +1222,7 @@ enum PanicMsgId { PanicMsgIdSliceWidenRemainder, PanicMsgIdUnwrapMaybeFail, PanicMsgIdUnwrapErrFail, + PanicMsgIdInvalidErrorCode, PanicMsgIdCount, }; @@ -1728,6 +1728,8 @@ enum IrInstructionId { IrInstructionIdIntToPtr, IrInstructionIdPtrToInt, IrInstructionIdIntToEnum, + IrInstructionIdIntToErr, + IrInstructionIdErrToInt, IrInstructionIdCheckSwitchProngs, IrInstructionIdTestType, IrInstructionIdTypeName, @@ -2404,6 +2406,18 @@ struct IrInstructionIntToEnum { IrInstruction *target; }; +struct IrInstructionIntToErr { + IrInstruction base; + + IrInstruction *target; +}; + +struct IrInstructionErrToInt { + IrInstruction base; + + IrInstruction *target; +}; + struct IrInstructionCheckSwitchProngsRange { IrInstruction *start; IrInstruction *end; diff --git a/src/codegen.cpp b/src/codegen.cpp index cbc0d4c0b0..ed4b0e0df2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -570,6 +570,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { return buf_create_from_str("attempt to unwrap error"); case PanicMsgIdUnreachable: return buf_create_from_str("reached unreachable code"); + case PanicMsgIdInvalidErrorCode: + return buf_create_from_str("invalid error code"); } zig_unreachable(); } @@ -1227,14 +1229,6 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, zig_unreachable(); case CastOpNoop: return expr_val; - case CastOpErrToInt: - assert(actual_type->id == TypeTableEntryIdErrorUnion); - if (!type_has_bits(actual_type->data.error.child_type)) { - return gen_widen_or_shorten(g, ir_want_debug_safety(g, &cast_instruction->base), - g->err_tag_type, wanted_type, expr_val); - } else { - zig_panic("TODO"); - } case CastOpResizeSlice: { assert(cast_instruction->tmp_ptr); @@ -1402,6 +1396,66 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, instruction->target->value.type, wanted_int_type, target_val); } +static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) { + TypeTableEntry *wanted_type = instruction->base.value.type; + assert(wanted_type->id == TypeTableEntryIdPureError); + + TypeTableEntry *actual_type = instruction->target->value.type; + assert(actual_type->id == TypeTableEntryIdInt); + assert(!actual_type->data.integral.is_signed); + + LLVMValueRef target_val = ir_llvm_value(g, instruction->target); + + if (ir_want_debug_safety(g, &instruction->base)) { + LLVMValueRef zero = LLVMConstNull(actual_type->type_ref); + LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, ""); + LLVMValueRef ok_bit; + uint64_t biggest_possible_err_val = max_unsigned_val(actual_type); + if (biggest_possible_err_val < g->error_decls.length) { + ok_bit = neq_zero_bit; + } else { + LLVMValueRef error_value_count = LLVMConstInt(actual_type->type_ref, g->error_decls.length, false); + LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, ""); + ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, ""); + } + + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); + + LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g, PanicMsgIdInvalidErrorCode); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + + return gen_widen_or_shorten(g, false, actual_type, g->err_tag_type, target_val); +} + +static LLVMValueRef ir_render_err_to_int(CodeGen *g, IrExecutable *executable, IrInstructionErrToInt *instruction) { + TypeTableEntry *wanted_type = instruction->base.value.type; + assert(wanted_type->id == TypeTableEntryIdInt); + assert(!wanted_type->data.integral.is_signed); + + TypeTableEntry *actual_type = instruction->target->value.type; + LLVMValueRef target_val = ir_llvm_value(g, instruction->target); + + if (actual_type->id == TypeTableEntryIdPureError) { + return gen_widen_or_shorten(g, ir_want_debug_safety(g, &instruction->base), + g->err_tag_type, wanted_type, target_val); + } else if (actual_type->id == TypeTableEntryIdErrorUnion) { + if (!type_has_bits(actual_type->data.error.child_type)) { + return gen_widen_or_shorten(g, ir_want_debug_safety(g, &instruction->base), + g->err_tag_type, wanted_type, target_val); + } else { + zig_panic("TODO"); + } + } else { + zig_unreachable(); + } +} + static LLVMValueRef ir_render_unreachable(CodeGen *g, IrExecutable *executable, IrInstructionUnreachable *unreachable_instruction) { @@ -2786,6 +2840,10 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_int_to_ptr(g, executable, (IrInstructionIntToPtr *)instruction); case IrInstructionIdIntToEnum: return ir_render_int_to_enum(g, executable, (IrInstructionIntToEnum *)instruction); + case IrInstructionIdIntToErr: + return ir_render_int_to_err(g, executable, (IrInstructionIntToErr *)instruction); + case IrInstructionIdErrToInt: + return ir_render_err_to_int(g, executable, (IrInstructionErrToInt *)instruction); case IrInstructionIdContainerInitList: return ir_render_container_init_list(g, executable, (IrInstructionContainerInitList *)instruction); case IrInstructionIdPanic: diff --git a/src/ir.cpp b/src/ir.cpp index 1fdb8f2e3b..a33fb3cd22 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -500,6 +500,14 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToEnum *) { return IrInstructionIdIntToEnum; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionIntToErr *) { + return IrInstructionIdIntToErr; +} + +static constexpr IrInstructionId ir_instruction_id(IrInstructionErrToInt *) { + return IrInstructionIdErrToInt; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckSwitchProngs *) { return IrInstructionIdCheckSwitchProngs; } @@ -2002,6 +2010,30 @@ static IrInstruction *ir_build_int_to_enum(IrBuilder *irb, Scope *scope, AstNode return &instruction->base; } +static IrInstruction *ir_build_int_to_err(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *target) +{ + IrInstructionIntToErr *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->target = target; + + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + +static IrInstruction *ir_build_err_to_int(IrBuilder *irb, Scope *scope, AstNode *source_node, + IrInstruction *target) +{ + IrInstructionErrToInt *instruction = ir_build_instruction( + irb, scope, source_node); + instruction->target = target; + + ir_ref_instruction(target, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_check_switch_prongs(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target_value, IrInstructionCheckSwitchProngsRange *ranges, size_t range_count) { @@ -2704,6 +2736,20 @@ static IrInstruction *ir_instruction_inttoenum_get_dep(IrInstructionIntToEnum *i } } +static IrInstruction *ir_instruction_inttoerr_get_dep(IrInstructionIntToErr *instruction, size_t index) { + switch (index) { + case 0: return instruction->target; + default: return nullptr; + } +} + +static IrInstruction *ir_instruction_errtoint_get_dep(IrInstructionErrToInt *instruction, size_t index) { + switch (index) { + case 0: return instruction->target; + default: return nullptr; + } +} + static IrInstruction *ir_instruction_checkswitchprongs_get_dep(IrInstructionCheckSwitchProngs *instruction, size_t index) { @@ -2938,6 +2984,10 @@ static IrInstruction *ir_instruction_get_dep(IrInstruction *instruction, size_t return ir_instruction_ptrtoint_get_dep((IrInstructionPtrToInt *) instruction, index); case IrInstructionIdIntToEnum: return ir_instruction_inttoenum_get_dep((IrInstructionIntToEnum *) instruction, index); + case IrInstructionIdIntToErr: + return ir_instruction_inttoerr_get_dep((IrInstructionIntToErr *) instruction, index); + case IrInstructionIdErrToInt: + return ir_instruction_errtoint_get_dep((IrInstructionErrToInt *) instruction, index); case IrInstructionIdCheckSwitchProngs: return ir_instruction_checkswitchprongs_get_dep((IrInstructionCheckSwitchProngs *) instruction, index); case IrInstructionIdTestType: @@ -6001,20 +6051,6 @@ static void eval_const_expr_implicit_cast(CastOp cast_op, case CastOpBytesToSlice: // can't do it break; - case CastOpErrToInt: - { - uint64_t value; - if (other_type->id == TypeTableEntryIdErrorUnion) { - value = other_val->data.x_err_union.err ? other_val->data.x_err_union.err->value : 0; - } else if (other_type->id == TypeTableEntryIdPureError) { - value = other_val->data.x_pure_err->value; - } else { - zig_unreachable(); - } - bignum_init_unsigned(&const_val->data.x_bignum, value); - const_val->special = ConstValSpecialStatic; - break; - } case CastOpIntToFloat: bignum_cast_to_float(&const_val->data.x_bignum, &other_val->data.x_bignum); const_val->special = ConstValSpecialStatic; @@ -6707,6 +6743,87 @@ static IrInstruction *ir_analyze_number_to_literal(IrAnalyze *ira, IrInstruction return result; } +static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target) { + assert(target->value.type->id == TypeTableEntryIdInt); + assert(!target->value.type->data.integral.is_signed); + + if (instr_is_comptime(target)) { + ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); + if (!val) + return ira->codegen->invalid_instruction; + + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, ira->codegen->builtin_types.entry_pure_error); + + uint64_t index = val->data.x_bignum.data.x_uint; + if (index == 0 || index >= ira->codegen->error_decls.length) { + ir_add_error(ira, source_instr, + buf_sprintf("integer value %" PRIu64 " represents no error", index)); + return ira->codegen->invalid_instruction; + } + + AstNode *error_decl_node = ira->codegen->error_decls.at(index); + result->value.data.x_pure_err = error_decl_node->data.error_value_decl.err; + return result; + } + + IrInstruction *result = ir_build_int_to_err(&ira->new_irb, source_instr->scope, source_instr->source_node, target); + result->value.type = ira->codegen->builtin_types.entry_pure_error; + return result; +} + +static IrInstruction *ir_analyze_err_to_int(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *target, + TypeTableEntry *wanted_type) +{ + assert(wanted_type->id == TypeTableEntryIdInt); + + TypeTableEntry *err_type = target->value.type; + + if (instr_is_comptime(target)) { + ConstExprValue *val = ir_resolve_const(ira, target, UndefBad); + if (!val) + return ira->codegen->invalid_instruction; + + IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, + source_instr->source_node, wanted_type); + + ErrorTableEntry *err; + if (err_type->id == TypeTableEntryIdErrorUnion) { + err = val->data.x_err_union.err; + } else if (err_type->id == TypeTableEntryIdPureError) { + err = val->data.x_pure_err; + } else { + zig_unreachable(); + } + result->value.type = wanted_type; + uint64_t err_value = err ? err->value : 0; + bignum_init_unsigned(&result->value.data.x_bignum, err_value); + + if (!bignum_fits_in_bits(&result->value.data.x_bignum, + wanted_type->data.integral.bit_count, wanted_type->data.integral.is_signed)) + { + ir_add_error_node(ira, source_instr->source_node, + buf_sprintf("error code '%s' does not fit in '%s'", + buf_ptr(&err->name), buf_ptr(&wanted_type->name))); + return ira->codegen->invalid_instruction; + } + + return result; + } + + BigNum bn; + bignum_init_unsigned(&bn, ira->codegen->error_decls.length); + if (!bignum_fits_in_bits(&bn, wanted_type->data.integral.bit_count, wanted_type->data.integral.is_signed)) { + ir_add_error_node(ira, source_instr->source_node, + buf_sprintf("too many error values to fit in '%s'", buf_ptr(&wanted_type->name))); + return ira->codegen->invalid_instruction; + } + + IrInstruction *result = ir_build_err_to_int(&ira->new_irb, source_instr->scope, source_instr->source_node, target); + result->value.type = wanted_type; + return result; +} + static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_instr, TypeTableEntry *wanted_type, IrInstruction *value) { @@ -6781,7 +6898,7 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpFloatToInt, false); } - // explicit cast from array to slice + // explicit cast from [N]T to []const T if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdArray) { TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry; assert(ptr_type->id == TypeTableEntryIdPointer); @@ -6909,17 +7026,14 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst if ((actual_type_is_void_err || actual_type_is_pure_err) && wanted_type->id == TypeTableEntryIdInt) { - BigNum bn; - bignum_init_unsigned(&bn, ira->codegen->error_decls.length); - if (bignum_fits_in_bits(&bn, wanted_type->data.integral.bit_count, - wanted_type->data.integral.is_signed)) - { - return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpErrToInt, false); - } else { - ir_add_error_node(ira, source_instr->source_node, - buf_sprintf("too many error values to fit in '%s'", buf_ptr(&wanted_type->name))); - return ira->codegen->invalid_instruction; - } + return ir_analyze_err_to_int(ira, source_instr, value, wanted_type); + } + + // explicit cast from integer to pure error + if (wanted_type->id == TypeTableEntryIdPureError && actual_type->id == TypeTableEntryIdInt && + !actual_type->data.integral.is_signed) + { + return ir_analyze_int_to_err(ira, source_instr, value); } // explicit cast from integer to enum type with no payload @@ -7843,7 +7957,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc result_type = ira->codegen->builtin_types.entry_invalid; } - bool is_comptime_var = ir_get_var_is_comptime(var); + bool is_comptime_var = ir_get_var_is_comptime(var); switch (result_type->id) { case TypeTableEntryIdTypeDecl: @@ -7852,6 +7966,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc break; // handled above case TypeTableEntryIdNumLitFloat: case TypeTableEntryIdNumLitInt: + case TypeTableEntryIdUndefLit: if (is_export || is_extern || (!var->src_is_const && !is_comptime_var)) { ir_add_error_node(ira, source_node, buf_sprintf("unable to infer variable type")); result_type = ira->codegen->builtin_types.entry_invalid; @@ -7873,7 +7988,6 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc result_type = ira->codegen->builtin_types.entry_invalid; } break; - case TypeTableEntryIdUndefLit: case TypeTableEntryIdVoid: case TypeTableEntryIdBool: case TypeTableEntryIdInt: @@ -10765,6 +10879,8 @@ static TypeTableEntry *ir_analyze_instruction_container_init_list(IrAnalyze *ira TypeTableEntry *this_field_type = field->type_entry; IrInstruction *init_value = instruction->items[0]->other; + if (type_is_invalid(init_value->value.type)) + return ira->codegen->builtin_types.entry_invalid; IrInstruction *casted_init_value = ir_implicit_cast(ira, init_value, this_field_type); if (casted_init_value == ira->codegen->invalid_instruction) @@ -12311,6 +12427,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi case IrInstructionIdIntToPtr: case IrInstructionIdPtrToInt: case IrInstructionIdIntToEnum: + case IrInstructionIdIntToErr: + case IrInstructionIdErrToInt: case IrInstructionIdStructInit: case IrInstructionIdStructFieldPtr: case IrInstructionIdEnumFieldPtr: @@ -12650,6 +12768,8 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdPtrToInt: case IrInstructionIdIntToPtr: case IrInstructionIdIntToEnum: + case IrInstructionIdIntToErr: + case IrInstructionIdErrToInt: case IrInstructionIdTestType: case IrInstructionIdTypeName: case IrInstructionIdCanImplicitCast: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 7814064f85..fd912d6919 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -799,6 +799,16 @@ static void ir_print_int_to_enum(IrPrint *irp, IrInstructionIntToEnum *instructi fprintf(irp->f, ")"); } +static void ir_print_int_to_err(IrPrint *irp, IrInstructionIntToErr *instruction) { + fprintf(irp->f, "inttoerr "); + ir_print_other_instruction(irp, instruction->target); +} + +static void ir_print_err_to_int(IrPrint *irp, IrInstructionErrToInt *instruction) { + fprintf(irp->f, "errtoint "); + ir_print_other_instruction(irp, instruction->target); +} + static void ir_print_check_switch_prongs(IrPrint *irp, IrInstructionCheckSwitchProngs *instruction) { fprintf(irp->f, "@checkSwitchProngs("); ir_print_other_instruction(irp, instruction->target_value); @@ -1117,6 +1127,12 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdIntToEnum: ir_print_int_to_enum(irp, (IrInstructionIntToEnum *)instruction); break; + case IrInstructionIdIntToErr: + ir_print_int_to_err(irp, (IrInstructionIntToErr *)instruction); + break; + case IrInstructionIdErrToInt: + ir_print_err_to_int(irp, (IrInstructionErrToInt *)instruction); + break; case IrInstructionIdCheckSwitchProngs: ir_print_check_switch_prongs(irp, (IrInstructionCheckSwitchProngs *)instruction); break; diff --git a/std/build.zig b/std/build.zig index 746800668a..8daac949c2 100644 --- a/std/build.zig +++ b/std/build.zig @@ -3,8 +3,12 @@ const mem = @import("mem.zig"); const debug = @import("debug.zig"); const List = @import("list.zig").List; const Allocator = @import("mem.zig").Allocator; +const os = @import("os/index.zig"); +const StdIo = os.ChildProcess.StdIo; +const Term = os.ChildProcess.Term; error ExtraArg; +error UncleanExit; pub const Builder = struct { zig_exe: []const u8, @@ -33,9 +37,9 @@ pub const Builder = struct { return exe; } - pub fn make(self: &Builder, args: []const []const u8) -> %void { + pub fn make(self: &Builder, cli_args: []const []const u8) -> %void { var verbose = false; - for (args) |arg| { + for (cli_args) |arg| { if (mem.eql(u8, arg, "--verbose")) { verbose = true; } else { @@ -44,7 +48,27 @@ pub const Builder = struct { } } for (self.exe_list.toSlice()) |exe| { - %%io.stderr.printf("TODO: invoke this command:\nzig build_exe {} --name {}\n", exe.root_src, exe.name); + var zig_args = List([]const u8).init(self.allocator); + defer zig_args.deinit(); + + %return zig_args.append("build_exe"[0...]); // TODO issue #296 + %return zig_args.append(exe.root_src); + %return zig_args.append("--name"[0...]); // TODO issue #296 + %return zig_args.append(exe.name); + + printInvocation(self.zig_exe, zig_args); + const TODO_env: []const []const u8 = undefined; // TODO + var child = %return os.ChildProcess.spawn(self.zig_exe, zig_args.toSliceConst(), TODO_env, + StdIo.Ignore, StdIo.Inherit, StdIo.Inherit); + const term = %return child.wait(); + switch (term) { + Term.Clean => |code| { + if (code != 0) { + return error.UncleanExit; + } + }, + else => return error.UncleanExit, + } } } }; @@ -57,3 +81,11 @@ const Exe = struct { fn handleErr(err: error) -> noreturn { debug.panic("error: {}\n", @errorName(err)); } + +fn printInvocation(exe_name: []const u8, args: &const List([]const u8)) { + %%io.stderr.printf("{}", exe_name); + for (args.toSliceConst()) |arg| { + %%io.stderr.printf(" {}", arg); + } + %%io.stderr.printf("\n"); +} diff --git a/std/io.zig b/std/io.zig index 109ed4072e..89b3a8c21f 100644 --- a/std/io.zig +++ b/std/io.zig @@ -13,22 +13,18 @@ const mem = @import("mem.zig"); const Buffer0 = @import("cstr.zig").Buffer0; const fmt = @import("fmt.zig"); -pub const stdin_fileno = 0; -pub const stdout_fileno = 1; -pub const stderr_fileno = 2; - pub var stdin = InStream { - .fd = stdin_fileno, + .fd = system.STDIN_FILENO, }; pub var stdout = OutStream { - .fd = stdout_fileno, + .fd = system.STDOUT_FILENO, .buffer = undefined, .index = 0, }; pub var stderr = OutStream { - .fd = stderr_fileno, + .fd = system.STDERR_FILENO, .buffer = undefined, .index = 0, }; @@ -234,7 +230,6 @@ pub const InStream = struct { if (read_err > 0) { switch (read_err) { errno.EINTR => continue, - errno.EINVAL => unreachable, errno.EFAULT => unreachable, errno.EBADF => return error.BadFd, @@ -247,7 +242,7 @@ pub const InStream = struct { } return index; }, - else => @compileError("unsupported OS"), + else => @compileError("Unsupported OS"), } } diff --git a/std/os/darwin.zig b/std/os/darwin.zig index 647fee40e2..fca34960bf 100644 --- a/std/os/darwin.zig +++ b/std/os/darwin.zig @@ -6,6 +6,10 @@ const arch = switch (@compileVar("arch")) { const errno = @import("errno.zig"); +pub const STDIN_FILENO = 0; +pub const STDOUT_FILENO = 1; +pub const STDERR_FILENO = 2; + pub const O_LARGEFILE = 0x0000; pub const O_RDONLY = 0x0000; diff --git a/std/os/index.zig b/std/os/index.zig index 63e74924fa..baa43e23de 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -7,12 +7,26 @@ pub const posix = switch(@compileVar("os")) { Os.windows => windows, else => @compileError("Unsupported OS"), }; +const debug = @import("../debug.zig"); +const assert = debug.assert; const errno = @import("errno.zig"); const linking_libc = @import("../target.zig").linking_libc; const c = @import("../c/index.zig"); +const mem = @import("../mem.zig"); +const Allocator = mem.Allocator; + +const io = @import("../io.zig"); + error Unexpected; +error SysResources; +error AccessDenied; +error InvalidExe; +error FileSystem; +error IsDir; +error FileNotFound; +error FileBusy; /// Fills `buf` with random bytes. If linking against libc, this calls the /// appropriate OS-specific library call. Otherwise it uses the zig standard @@ -76,3 +90,332 @@ pub coldcc fn abort() -> noreturn { else => @compileError("Unsupported OS"), } } + +fn makePipe() -> %[2]i32 { + var fds: [2]i32 = undefined; + const err = posix.getErrno(posix.pipe(&fds)); + if (err > 0) { + return switch (err) { + errno.EMFILE, errno.ENFILE => error.SysResources, + else => error.Unexpected, + } + } + return fds; +} + +fn destroyPipe(pipe: &const [2]i32) { + closeNoIntr((*pipe)[0]); + closeNoIntr((*pipe)[1]); +} + +fn closeNoIntr(fd: i32) { + while (true) { + const err = posix.getErrno(posix.close(fd)); + if (err == errno.EINTR) { + continue; + } else { + return; + } + } +} + +fn openNoIntr(path: []const u8, flags: usize, perm: usize) -> %i32 { + while (true) { + const result = posix.open(path, flags, perm); + const err = posix.getErrno(result); + if (err > 0) { + return switch (err) { + errno.EINTR => continue, + + errno.EFAULT => unreachable, + errno.EINVAL => unreachable, + errno.EACCES => error.BadPerm, + errno.EFBIG, errno.EOVERFLOW => error.FileTooBig, + errno.EISDIR => error.IsDir, + errno.ELOOP => error.SymLinkLoop, + errno.EMFILE => error.ProcessFdQuotaExceeded, + errno.ENAMETOOLONG => error.NameTooLong, + errno.ENFILE => error.SystemFdQuotaExceeded, + errno.ENODEV => error.NoDevice, + errno.ENOENT => error.PathNotFound, + errno.ENOMEM => error.NoMem, + errno.ENOSPC => error.NoSpaceLeft, + errno.ENOTDIR => error.NotDir, + errno.EPERM => error.BadPerm, + else => error.Unexpected, + } + } + return i32(result); + } +} + +const ErrInt = @intType(false, @sizeOf(error) * 8); +fn writeIntFd(fd: i32, value: ErrInt) -> %void { + var bytes: [@sizeOf(ErrInt)]u8 = undefined; + mem.writeInt(bytes[0...], value, true); + + var index: usize = 0; + while (index < bytes.len) { + const amt_written = posix.write(fd, &bytes[index], bytes.len - index); + const err = posix.getErrno(amt_written); + if (err > 0) { + switch (err) { + errno.EINTR => continue, + errno.EINVAL => unreachable, + else => return error.SysResources, + } + } + index += amt_written; + } +} + +fn readIntFd(fd: i32) -> %ErrInt { + var bytes: [@sizeOf(ErrInt)]u8 = undefined; + + var index: usize = 0; + while (index < bytes.len) { + const amt_written = posix.read(fd, &bytes[index], bytes.len - index); + const err = posix.getErrno(amt_written); + if (err > 0) { + switch (err) { + errno.EINTR => continue, + errno.EINVAL => unreachable, + else => return error.SysResources, + } + } + index += amt_written; + } + + return mem.readInt(bytes[0...], ErrInt, true); +} + +// Child of fork calls this to report an error to the fork parent. +// Then the child exits. +fn forkChildErrReport(fd: i32, err: error) -> noreturn { + _ = writeIntFd(fd, ErrInt(err)); + posix.exit(1); +} + +fn dup2NoIntr(old_fd: i32, new_fd: i32) -> %void { + while (true) { + const err = posix.getErrno(posix.dup2(old_fd, new_fd)); + if (err > 0) { + return switch (err) { + errno.EBUSY, errno.EINTR => continue, + errno.EMFILE => error.SysResources, + errno.EINVAL => unreachable, + else => error.Unexpected, + }; + } + return; + } +} + +pub const ChildProcess = struct { + pid: i32, + err_pipe: [2]i32, + + stdin: ?io.OutStream, + stdout: ?io.InStream, + stderr: ?io.InStream, + + pub const Term = enum { + Clean: i32, + Signal: i32, + Stopped: i32, + Unknown: i32, + }; + + pub const StdIo = enum { + Inherit, + Ignore, + Pipe, + Close, + }; + + pub fn spawn(exe_path: []const u8, args: []const []const u8, env: []const []const u8, + stdin: StdIo, stdout: StdIo, stderr: StdIo) -> %ChildProcess + { + switch (@compileVar("os")) { + Os.linux, Os.macosx, Os.ios, Os.darwin => { + return spawnPosix(exe_path, args, env, stdin, stdout, stderr); + }, + else => @compileError("Unsupported OS"), + } + } + + pub fn wait(self: &ChildProcess) -> %Term { + defer { + closeNoIntr(self.err_pipe[0]); + closeNoIntr(self.err_pipe[1]); + }; + + var status: i32 = undefined; + while (true) { + const err = posix.getErrno(posix.waitpid(self.pid, &status, 0)); + if (err > 0) { + switch (err) { + errno.EINVAL, errno.ECHILD => unreachable, + errno.EINTR => continue, + else => { + if (const *stdin ?= self.stdin) { stdin.close(); } + if (const *stdout ?= self.stdin) { stdout.close(); } + if (const *stderr ?= self.stdin) { stderr.close(); } + return error.Unexpected; + }, + } + } + break; + } + + if (const *stdin ?= self.stdin) { stdin.close(); } + if (const *stdout ?= self.stdin) { stdout.close(); } + if (const *stderr ?= self.stdin) { stderr.close(); } + + // Write @maxValue(ErrInt) to the write end of the err_pipe. This is after + // waitpid, so this write is guaranteed to be after the child + // pid potentially wrote an error. This way we can do a blocking + // read on the error pipe and either get @maxValue(ErrInt) (no error) or + // an error code. + %return writeIntFd(self.err_pipe[1], @maxValue(ErrInt)); + const err_int = %return readIntFd(self.err_pipe[0]); + // Here we potentially return the fork child's error + // from the parent pid. + if (err_int != @maxValue(ErrInt)) { + return error(err_int); + } + + return statusToTerm(status); + } + + fn statusToTerm(status: i32) -> Term { + return if (posix.WIFEXITED(status)) { + Term.Clean { posix.WEXITSTATUS(status) } + } else if (posix.WIFSIGNALED(status)) { + Term.Signal { posix.WTERMSIG(status) } + } else if (posix.WIFSTOPPED(status)) { + Term.Stopped { posix.WSTOPSIG(status) } + } else { + Term.Unknown { status } + }; + } + + fn spawnPosix(exe_path: []const u8, args: []const []const u8, env: []const []const u8, + stdin: StdIo, stdout: StdIo, stderr: StdIo) -> %ChildProcess + { + // TODO issue #295 + //const stdin_pipe = if (stdin == StdIo.Pipe) %return makePipe() else undefined; + var stdin_pipe: [2]i32 = undefined; + if (stdin == StdIo.Pipe) + stdin_pipe = %return makePipe(); + %defer if (stdin == StdIo.Pipe) { destroyPipe(stdin_pipe); }; + + // TODO issue #295 + //const stdout_pipe = if (stdout == StdIo.Pipe) %return makePipe() else undefined; + var stdout_pipe: [2]i32 = undefined; + if (stdout == StdIo.Pipe) + stdout_pipe = %return makePipe(); + %defer if (stdout == StdIo.Pipe) { destroyPipe(stdout_pipe); }; + + // TODO issue #295 + //const stderr_pipe = if (stderr == StdIo.Pipe) %return makePipe() else undefined; + var stderr_pipe: [2]i32 = undefined; + if (stderr == StdIo.Pipe) + stderr_pipe = %return makePipe(); + %defer if (stderr == StdIo.Pipe) { destroyPipe(stderr_pipe); }; + + const any_ignore = (stdin == StdIo.Ignore or stdout == StdIo.Ignore or stderr == StdIo.Ignore); + // TODO issue #295 + //const dev_null_fd = if (any_ignore) { + // %return openNoIntr("/dev/null", posix.O_RDWR, 0) + //} else { + // undefined + //}; + var dev_null_fd: i32 = undefined; + if (any_ignore) + dev_null_fd = %return openNoIntr("/dev/null", posix.O_RDWR, 0); + + // This pipe is used to communicate errors between the time of fork + // and execve from the child process to the parent process. + const err_pipe = %return makePipe(); + %defer destroyPipe(err_pipe); + + const pid = posix.fork(); + const pid_err = linux.getErrno(pid); + if (pid_err > 0) { + return switch (pid_err) { + errno.EAGAIN, errno.ENOMEM, errno.ENOSYS => error.SysResources, + else => error.Unexpected, + }; + } + if (pid == 0) { + // we are the child + setUpChildIo(stdin, stdin_pipe[0], posix.STDIN_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + setUpChildIo(stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) %% + |err| forkChildErrReport(err_pipe[1], err); + + const err = posix.getErrno(posix.execve(exe_path, args, env)); + assert(err > 0); + forkChildErrReport(err_pipe[1], switch (err) { + errno.EFAULT => unreachable, + errno.E2BIG, errno.EMFILE, errno.ENAMETOOLONG, errno.ENFILE, errno.ENOMEM => error.SysResources, + errno.EACCES, errno.EPERM => error.AccessDenied, + errno.EINVAL, errno.ENOEXEC => error.InvalidExe, + errno.EIO, errno.ELOOP => error.FileSystem, + errno.EISDIR => error.IsDir, + errno.ENOENT, errno.ENOTDIR => error.FileNotFound, + errno.ETXTBSY => error.FileBusy, + else => error.Unexpected, + }); + } + + // we are the parent + if (stdin == StdIo.Pipe) { closeNoIntr(stdin_pipe[0]); } + if (stdout == StdIo.Pipe) { closeNoIntr(stdout_pipe[1]); } + if (stderr == StdIo.Pipe) { closeNoIntr(stderr_pipe[1]); } + if (any_ignore) { closeNoIntr(dev_null_fd); } + + return ChildProcess { + .pid = i32(pid), + .err_pipe = err_pipe, + + .stdin = if (stdin == StdIo.Pipe) { + io.OutStream { + .fd = stdin_pipe[1], + } + } else { + null + }, + .stdout = if (stdout == StdIo.Pipe) { + io.InStream { + .fd = stdout_pipe[0], + .buffer = undefined, + .index = 0, + } + } else { + null + }, + .stderr = if (stderr == StdIo.Pipe) { + io.InStream { + .fd = stderr_pipe[0], + .buffer = undefined, + .index = 0, + } + } else { + null + }, + }; + } + + fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) -> %void { + switch (stdio) { + StdIo.Pipe => %return dup2NoIntr(pipe_fd, std_fileno), + StdIo.Close => closeNoIntr(std_fileno), + StdIo.Inherit => {}, + StdIo.Ignore => %return dup2NoIntr(dev_null_fd, std_fileno), + } + } +}; diff --git a/std/os/linux.zig b/std/os/linux.zig index a42bebe4c8..8e1bf54aee 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -5,6 +5,10 @@ const arch = switch (@compileVar("arch")) { }; const errno = @import("errno.zig"); +pub const STDIN_FILENO = 0; +pub const STDOUT_FILENO = 1; +pub const STDERR_FILENO = 2; + pub const PROT_NONE = 0; pub const PROT_READ = 1; pub const PROT_WRITE = 2; @@ -237,12 +241,66 @@ pub const AF_NFC = PF_NFC; pub const AF_VSOCK = PF_VSOCK; pub const AF_MAX = PF_MAX; + +fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) } +fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) } +pub fn WEXITSTATUS(s: i32) -> i32 { signed((unsigned(s) & 0xff00) >> 8) } +pub fn WTERMSIG(s: i32) -> i32 { signed(unsigned(s) & 0x7f) } +pub fn WSTOPSIG(s: i32) -> i32 { WEXITSTATUS(s) } +pub fn WIFEXITED(s: i32) -> bool { WTERMSIG(s) == 0 } +pub fn WIFSTOPPED(s: i32) -> bool { (u16)(((unsigned(s)&0xffff)*%0x10001)>>8) > 0x7f00 } +pub fn WIFSIGNALED(s: i32) -> bool { (unsigned(s)&0xffff)-%1 < 0xff } + /// Get the errno from a syscall return value, or 0 for no error. pub fn getErrno(r: usize) -> usize { const signed_r = *@ptrcast(&isize, &r); if (signed_r > -4096 and signed_r < 0) usize(-signed_r) else 0 } +pub fn dup2(old: i32, new: i32) -> usize { + arch.syscall2(arch.SYS_dup2, usize(old), usize(new)) +} + +pub fn execve_c(path: &const u8, argv: &const ?&const u8, envp: &const ?&const u8) -> usize { + arch.syscall3(arch.SYS_execve, path, argv, envp) +} + +/// This function must allocate memory to add a null terminating bytes on path, each arg, +/// and each environment variable line, as well as a null pointer after the arg list and +/// environment variable list. We allocate stack memory since the process is about to get +/// wiped anyway. +pub fn execve(path: []const u8, argv: []const []const u8, envp: []const []const u8) -> usize { + const path_buf = @alloca(u8, path.len + 1); + @memcpy(&path_buf[0], &path[0], path.len); + path_buf[path.len] = 0; + + const argv_buf = @alloca([]const ?&const u8, argv.len + 1); + for (argv) |arg, i| { + const arg_buf = @alloca(u8, arg.len + 1); + @memcpy(&arg_buf[0], &arg[0], arg.len); + arg_buf[arg.len] = 0; + + argv[i] = arg_buf; + } + argv_buf[argv.len] = null; + + const envp_buf = @alloca([]const ?&const u8, envp.len + 1); + for (envp) |env, i| { + const env_buf = @alloca(u8, env.len + 1); + @memcpy(&env_buf[0], &env[0], env.len); + env_buf[env.len] = 0; + + envp[i] = env_buf; + } + envp_buf[envp.len] = null; + + return execve_c(path_buf.ptr, argv_buf.ptr, envp_buf.ptr); +} + +pub fn fork() -> usize { + arch.syscall0(arch.SYS_fork) +} + pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize) -> usize { @@ -261,6 +319,14 @@ pub fn pread(fd: i32, buf: &u8, count: usize, offset: usize) -> usize { arch.syscall4(arch.SYS_pread, usize(fd), usize(buf), count, offset) } +pub fn pipe(fd: &[2]i32) -> usize { + pipe2(fd, 0) +} + +pub fn pipe2(fd: &[2]i32, flags: usize) -> usize { + arch.syscall2(arch.SYS_pipe2, usize(fd), flags) +} + pub fn write(fd: i32, buf: &const u8, count: usize) -> usize { arch.syscall3(arch.SYS_write, usize(fd), usize(buf), count) } @@ -319,8 +385,12 @@ pub fn getrandom(buf: &u8, count: usize, flags: u32) -> usize { arch.syscall3(arch.SYS_getrandom, usize(buf), count, usize(flags)) } -pub fn kill(pid: i32, sig: i32) -> i32 { - i32(arch.syscall2(arch.SYS_kill, usize(pid), usize(sig))) +pub fn kill(pid: i32, sig: i32) -> usize { + arch.syscall2(arch.SYS_kill, usize(pid), usize(sig)) +} + +pub fn waitpid(pid: i32, status: &i32, options: i32) -> usize { + arch.syscall4(arch.SYS_wait4, usize(pid), usize(status), usize(options), 0) } const NSIG = 65; diff --git a/test/cases/cast.zig b/test/cases/cast.zig index 030dd68150..12fb0d9ee4 100644 --- a/test/cases/cast.zig +++ b/test/cases/cast.zig @@ -30,3 +30,14 @@ test "implicitly cast a pointer to a const pointer of it" { fn funcWithConstPtrPtr(x: &const &i32) { **x += 1; } + +error ItBroke; +test "explicit cast from integer to error type" { + testCastIntToErr(error.ItBroke); + comptime testCastIntToErr(error.ItBroke); +} +fn testCastIntToErr(err: error) { + const x = usize(err); + const y = error(x); + assert(error.ItBroke == y); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp index e56d6625c7..85ef5c7ff9 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -1758,6 +1758,13 @@ export fn foo() { } )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to infer variable type"); + add_compile_fail_case("undefined literal on a non-comptime var", R"SOURCE( +export fn foo() { + var i = undefined; + i = i32(1); +} + )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to infer variable type"); + add_compile_fail_case("dereference an array", R"SOURCE( var s_buffer: [10]u8 = undefined; pub fn pass(in: []u8) -> []u8 { @@ -1818,6 +1825,15 @@ export fn entry(a: &i32) -> usize { return @ptrcast(usize, a); } )SOURCE", 1, ".tmp_source.zig:3:21: error: expected pointer, found 'usize'"); + + add_compile_fail_case("too many error values to cast to small integer", R"SOURCE( +error A; error B; error C; error D; error E; error F; error G; error H; +const u2 = @intType(false, 2); +fn foo(e: error) -> u2 { + return u2(e); +} +export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + )SOURCE", 1, ".tmp_source.zig:5:14: error: too many error values to fit in 'u2'"); } ////////////////////////////////////////////////////////////////////////////// @@ -2043,6 +2059,18 @@ fn bar() -> %void { } )SOURCE"); + add_debug_safety_case("cast integer to error and no code matches", R"SOURCE( +pub fn panic(message: []const u8) -> noreturn { + @breakpoint(); + while (true) {} +} +pub fn main(args: [][]u8) -> %void { + _ = bar(9999); +} +fn bar(x: u32) -> error { + return error(x); +} + )SOURCE"); } //////////////////////////////////////////////////////////////////////////////