Merge branch 'master' into llvm9

This commit is contained in:
Andrew Kelley 2019-08-20 19:09:52 -04:00
commit 5f3d59f0ac
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
29 changed files with 728 additions and 208 deletions

View File

@ -6379,7 +6379,7 @@ comptime {
{#header_close#} {#header_close#}
{#header_open|@asyncCall#} {#header_open|@asyncCall#}
<pre>{#syntax#}@asyncCall(frame_buffer: []u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}</pre> <pre>{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}</pre>
<p> <p>
{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer, {#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer,
which may or may not be an {#link|async function|Async Functions#}. which may or may not be an {#link|async function|Async Functions#}.
@ -6405,7 +6405,7 @@ test "async fn pointer in a struct field" {
bar: async fn (*i32) void, bar: async fn (*i32) void,
}; };
var foo = Foo{ .bar = func }; var foo = Foo{ .bar = func };
var bytes: [64]u8 = undefined; var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined;
const f = @asyncCall(&bytes, {}, foo.bar, &data); const f = @asyncCall(&bytes, {}, foo.bar, &data);
assert(data == 2); assert(data == 2);
resume f; resume f;
@ -7322,17 +7322,22 @@ mem.set(u8, dest, c);{#endsyntax#}</pre>
{#header_close#} {#header_close#}
{#header_open|@newStackCall#} {#header_open|@newStackCall#}
<pre>{#syntax#}@newStackCall(new_stack: []u8, function: var, args: ...) var{#endsyntax#}</pre> <pre>{#syntax#}@newStackCall(new_stack: []align(target_stack_align) u8, function: var, args: ...) var{#endsyntax#}</pre>
<p> <p>
This calls a function, in the same way that invoking an expression with parentheses does. However, This calls a function, in the same way that invoking an expression with parentheses does. However,
instead of using the same stack as the caller, the function uses the stack provided in the {#syntax#}new_stack{#endsyntax#} instead of using the same stack as the caller, the function uses the stack provided in the {#syntax#}new_stack{#endsyntax#}
parameter. parameter.
</p> </p>
<p>
The new stack must be aligned to {#syntax#}target_stack_align{#endsyntax#} bytes. This is a target-specific
number. A safe value that will work on all targets is {#syntax#}16{#endsyntax#}. This value can
also be obtained by using {#link|@sizeOf#} on the {#link|@Frame#} type of {#link|Async Functions#}.
</p>
{#code_begin|test#} {#code_begin|test#}
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
var new_stack_bytes: [1024]u8 = undefined; var new_stack_bytes: [1024]u8 align(16) = undefined;
test "calling a function with a new stack" { test "calling a function with a new stack" {
const arg = 1234; const arg = 1234;
@ -9318,6 +9323,13 @@ const c = @cImport({
<li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#} <li>Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#}
please!</li> please!</li>
</ul> </ul>
<p>When a C pointer is pointing to a single struct (not an array), deference the C pointer to
access to the struct's fields or member data. That syntax looks like
this: </p>
<p>{#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}</p>
<p>This is comparable to doing {#syntax#}->{#endsyntax#} in C.</p>
<p> When a C pointer is pointing to an array of structs, the syntax reverts to this:</p>
<p>{#syntax#}ptr_to_struct_array[index].struct_member{#endsyntax#}</p>
{#header_close#} {#header_close#}
{#header_open|Exporting a C Library#} {#header_open|Exporting a C Library#}

View File

@ -1279,6 +1279,12 @@ struct ZigTypeOpaque {
struct ZigTypeFnFrame { struct ZigTypeFnFrame {
ZigFn *fn; ZigFn *fn;
ZigType *locals_struct; ZigType *locals_struct;
// This is set to the type that resolving the frame currently depends on, null if none.
// It's for generating a helpful error message.
ZigType *resolve_loop_type;
AstNode *resolve_loop_src_node;
bool reported_loop_err;
}; };
struct ZigTypeAnyFrame { struct ZigTypeAnyFrame {
@ -1396,6 +1402,7 @@ struct ZigFn {
AstNode *set_cold_node; AstNode *set_cold_node;
const AstNode *inferred_async_node; const AstNode *inferred_async_node;
ZigFn *inferred_async_fn; ZigFn *inferred_async_fn;
AstNode *non_async_node;
ZigList<GlobalExport> export_list; ZigList<GlobalExport> export_list;
ZigList<IrInstructionCallGen *> call_list; ZigList<IrInstructionCallGen *> call_list;

View File

@ -4144,8 +4144,15 @@ void semantic_analyze(CodeGen *g) {
// second pass over functions for detecting async // second pass over functions for detecting async
for (g->fn_defs_index = 0; g->fn_defs_index < g->fn_defs.length; g->fn_defs_index += 1) { for (g->fn_defs_index = 0; g->fn_defs_index < g->fn_defs.length; g->fn_defs_index += 1) {
ZigFn *fn_entry = g->fn_defs.at(g->fn_defs_index); ZigFn *fn = g->fn_defs.at(g->fn_defs_index);
analyze_fn_async(g, fn_entry, true); analyze_fn_async(g, fn, true);
if (fn_is_async(fn) && fn->non_async_node != nullptr) {
ErrorMsg *msg = add_node_error(g, fn->proto_node,
buf_sprintf("'%s' cannot be async", buf_ptr(&fn->symbol_name)));
add_error_note(g, msg, fn->non_async_node,
buf_sprintf("required to be non-async here"));
add_async_error_notes(g, msg, fn);
}
} }
} }
@ -5190,6 +5197,27 @@ static ZigType *get_async_fn_type(CodeGen *g, ZigType *orig_fn_type) {
return fn_type; return fn_type;
} }
static void emit_error_notes_for_type_loop(CodeGen *g, ErrorMsg *msg, ZigType *stop_type,
ZigType *ty, AstNode *src_node)
{
ErrorMsg *note = add_error_note(g, msg, src_node,
buf_sprintf("when analyzing type '%s' here", buf_ptr(&ty->name)));
if (ty == stop_type)
return;
switch (ty->id) {
case ZigTypeIdFnFrame: {
ty->data.frame.reported_loop_err = true;
ZigType *depending_type = ty->data.frame.resolve_loop_type;
if (depending_type == nullptr)
return;
emit_error_notes_for_type_loop(g, note, stop_type,
depending_type, ty->data.frame.resolve_loop_src_node);
}
default:
return;
}
}
static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
Error err; Error err;
@ -5199,6 +5227,20 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
ZigFn *fn = frame_type->data.frame.fn; ZigFn *fn = frame_type->data.frame.fn;
assert(!fn->type_entry->data.fn.is_generic); assert(!fn->type_entry->data.fn.is_generic);
if (frame_type->data.frame.resolve_loop_type != nullptr) {
if (!frame_type->data.frame.reported_loop_err) {
frame_type->data.frame.reported_loop_err = true;
ErrorMsg *msg = add_node_error(g, fn->proto_node,
buf_sprintf("'%s' depends on itself", buf_ptr(&frame_type->name)));
emit_error_notes_for_type_loop(g, msg,
frame_type,
frame_type->data.frame.resolve_loop_type,
frame_type->data.frame.resolve_loop_src_node);
emit_error_notes_for_ref_stack(g, msg);
}
return ErrorSemanticAnalyzeFail;
}
switch (fn->anal_state) { switch (fn->anal_state) {
case FnAnalStateInvalid: case FnAnalStateInvalid:
return ErrorSemanticAnalyzeFail; return ErrorSemanticAnalyzeFail;
@ -5292,6 +5334,10 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
return ErrorSemanticAnalyzeFail; return ErrorSemanticAnalyzeFail;
} }
ZigType *callee_frame_type = get_fn_frame_type(g, callee);
frame_type->data.frame.resolve_loop_type = callee_frame_type;
frame_type->data.frame.resolve_loop_src_node = call->base.source_node;
analyze_fn_body(g, callee); analyze_fn_body(g, callee);
if (callee->anal_state == FnAnalStateInvalid) { if (callee->anal_state == FnAnalStateInvalid) {
frame_type->data.frame.locals_struct = g->builtin_types.entry_invalid; frame_type->data.frame.locals_struct = g->builtin_types.entry_invalid;
@ -5301,8 +5347,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
if (!fn_is_async(callee)) if (!fn_is_async(callee))
continue; continue;
ZigType *callee_frame_type = get_fn_frame_type(g, callee);
IrInstructionAllocaGen *alloca_gen = allocate<IrInstructionAllocaGen>(1); IrInstructionAllocaGen *alloca_gen = allocate<IrInstructionAllocaGen>(1);
alloca_gen->base.id = IrInstructionIdAllocaGen; alloca_gen->base.id = IrInstructionIdAllocaGen;
alloca_gen->base.source_node = call->base.source_node; alloca_gen->base.source_node = call->base.source_node;
@ -5371,9 +5415,13 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
continue; continue;
} }
} }
frame_type->data.frame.resolve_loop_type = child_type;
frame_type->data.frame.resolve_loop_src_node = instruction->base.source_node;
if ((err = type_resolve(g, child_type, ResolveStatusSizeKnown))) { if ((err = type_resolve(g, child_type, ResolveStatusSizeKnown))) {
return err; return err;
} }
const char *name; const char *name;
if (*instruction->name_hint == 0) { if (*instruction->name_hint == 0) {
name = buf_ptr(buf_sprintf("@local%" ZIG_PRI_usize, alloca_i)); name = buf_ptr(buf_sprintf("@local%" ZIG_PRI_usize, alloca_i));

View File

@ -6743,6 +6743,25 @@ static Error parse_asm_template(IrBuilder *irb, AstNode *source_node, Buf *asm_t
return ErrorNone; return ErrorNone;
} }
static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok, Buf *src_template) {
const char *ptr = buf_ptr(src_template) + tok->start + 2;
size_t len = tok->end - tok->start - 2;
size_t result = 0;
for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) {
AsmOutput *asm_output = node->data.asm_expr.output_list.at(i);
if (buf_eql_mem(asm_output->asm_symbolic_name, ptr, len)) {
return result;
}
}
for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1, result += 1) {
AsmInput *asm_input = node->data.asm_expr.input_list.at(i);
if (buf_eql_mem(asm_input->asm_symbolic_name, ptr, len)) {
return result;
}
}
return SIZE_MAX;
}
static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) { static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
Error err; Error err;
assert(node->type == NodeTypeAsmExpr); assert(node->type == NodeTypeAsmExpr);
@ -6830,6 +6849,22 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod
input_list[i] = input_value; input_list[i] = input_value;
} }
for (size_t token_i = 0; token_i < tok_list.length; token_i += 1) {
AsmToken asm_token = tok_list.at(token_i);
if (asm_token.id == AsmTokenIdVar) {
size_t index = find_asm_index(irb->codegen, node, &asm_token, template_buf);
if (index == SIZE_MAX) {
const char *ptr = buf_ptr(template_buf) + asm_token.start + 2;
uint32_t len = asm_token.end - asm_token.start - 2;
add_node_error(irb->codegen, node,
buf_sprintf("could not find '%.*s' in the inputs or outputs.",
len, ptr));
return irb->codegen->invalid_instruction;
}
}
}
return ir_build_asm(irb, scope, node, template_buf, tok_list.items, tok_list.length, return ir_build_asm(irb, scope, node, template_buf, tok_list.items, tok_list.length,
input_list, output_types, output_vars, return_count, is_volatile); input_list, output_types, output_vars, return_count, is_volatile);
} }
@ -9485,10 +9520,6 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
result.id = ConstCastResultIdFnAlign; result.id = ConstCastResultIdFnAlign;
return result; return result;
} }
if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) {
result.id = ConstCastResultIdFnCC;
return result;
}
if (wanted_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) { if (wanted_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) {
result.id = ConstCastResultIdFnVarArgs; result.id = ConstCastResultIdFnVarArgs;
return result; return result;
@ -9546,6 +9577,11 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
return result; return result;
} }
} }
if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) {
// ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok.
result.id = ConstCastResultIdFnCC;
return result;
}
return result; return result;
} }
@ -11780,8 +11816,11 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa
add_error_note(ira->codegen, parent_msg, source_node, add_error_note(ira->codegen, parent_msg, source_node,
buf_sprintf("only one of the functions is generic")); buf_sprintf("only one of the functions is generic"));
break; break;
case ConstCastResultIdFnCC:
add_error_note(ira->codegen, parent_msg, source_node,
buf_sprintf("calling convention mismatch"));
break;
case ConstCastResultIdFnAlign: // TODO case ConstCastResultIdFnAlign: // TODO
case ConstCastResultIdFnCC: // TODO
case ConstCastResultIdFnVarArgs: // TODO case ConstCastResultIdFnVarArgs: // TODO
case ConstCastResultIdFnReturnType: // TODO case ConstCastResultIdFnReturnType: // TODO
case ConstCastResultIdFnArgCount: // TODO case ConstCastResultIdFnArgCount: // TODO
@ -11891,6 +11930,21 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop); return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop);
} }
if (const_cast_result.id == ConstCastResultIdFnCC) {
ir_assert(value->value.type->id == ZigTypeIdFn, source_instr);
// ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok.
if (wanted_type->data.fn.fn_type_id.cc == CallingConventionAsync &&
actual_type->data.fn.fn_type_id.cc == CallingConventionUnspecified)
{
ir_assert(value->value.data.x_ptr.special == ConstPtrSpecialFunction, source_instr);
ZigFn *fn = value->value.data.x_ptr.data.fn.fn_entry;
if (fn->inferred_async_node == nullptr) {
fn->inferred_async_node = source_instr->source_node;
}
return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop);
}
}
// cast from T to ?T // cast from T to ?T
// note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism // note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism
if (wanted_type->id == ZigTypeIdOptional) { if (wanted_type->id == ZigTypeIdOptional) {
@ -12110,7 +12164,26 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst
array_type->data.array.child_type, source_node, array_type->data.array.child_type, source_node,
!slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk)
{ {
return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type, result_loc); // If the pointers both have ABI align, it works.
bool ok_align = slice_ptr_type->data.pointer.explicit_alignment == 0 &&
actual_type->data.pointer.explicit_alignment == 0;
if (!ok_align) {
// If either one has non ABI align, we have to resolve them both
if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type,
ResolveStatusAlignmentKnown)))
{
return ira->codegen->invalid_instruction;
}
if ((err = type_resolve(ira->codegen, slice_ptr_type->data.pointer.child_type,
ResolveStatusAlignmentKnown)))
{
return ira->codegen->invalid_instruction;
}
ok_align = get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, slice_ptr_type);
}
if (ok_align) {
return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type, result_loc);
}
} }
} }
@ -13902,8 +13975,7 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp *
uint64_t old_array_len = array_type->data.array.len; uint64_t old_array_len = array_type->data.array.len;
uint64_t new_array_len; uint64_t new_array_len;
if (mul_u64_overflow(old_array_len, mult_amt, &new_array_len)) if (mul_u64_overflow(old_array_len, mult_amt, &new_array_len)) {
{
ir_add_error(ira, &instruction->base, buf_sprintf("operation results in overflow")); ir_add_error(ira, &instruction->base, buf_sprintf("operation results in overflow"));
return ira->codegen->invalid_instruction; return ira->codegen->invalid_instruction;
} }
@ -13918,6 +13990,15 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp *
return result; return result;
} }
switch (type_has_one_possible_value(ira->codegen, result->value.type)) {
case OnePossibleValueInvalid:
return ira->codegen->invalid_instruction;
case OnePossibleValueYes:
return result;
case OnePossibleValueNo:
break;
}
// TODO optimize the buf case // TODO optimize the buf case
expand_undef_array(ira->codegen, array_val); expand_undef_array(ira->codegen, array_val);
out_val->data.x_array.data.s_none.elements = create_const_vals(new_array_len); out_val->data.x_array.data.s_none.elements = create_const_vals(new_array_len);
@ -13925,8 +14006,11 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp *
uint64_t i = 0; uint64_t i = 0;
for (uint64_t x = 0; x < mult_amt; x += 1) { for (uint64_t x = 0; x < mult_amt; x += 1) {
for (uint64_t y = 0; y < old_array_len; y += 1) { for (uint64_t y = 0; y < old_array_len; y += 1) {
copy_const_val(&out_val->data.x_array.data.s_none.elements[i], ConstExprValue *elem_dest_val = &out_val->data.x_array.data.s_none.elements[i];
&array_val->data.x_array.data.s_none.elements[y], false); copy_const_val(elem_dest_val, &array_val->data.x_array.data.s_none.elements[y], false);
elem_dest_val->parent.id = ConstParentIdArray;
elem_dest_val->parent.data.p_array.array_val = out_val;
elem_dest_val->parent.data.p_array.elem_index = i;
i += 1; i += 1;
} }
} }
@ -14753,6 +14837,9 @@ static IrInstruction *ir_resolve_result_raw(IrAnalyze *ira, IrInstruction *suspe
return ira->codegen->invalid_instruction; return ira->codegen->invalid_instruction;
} }
uint64_t parent_ptr_align = get_ptr_align(ira->codegen, parent_ptr_type); uint64_t parent_ptr_align = get_ptr_align(ira->codegen, parent_ptr_type);
if ((err = type_resolve(ira->codegen, value_type, ResolveStatusAlignmentKnown))) {
return ira->codegen->invalid_instruction;
}
ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value_type, ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value_type,
parent_ptr_type->data.pointer.is_const, parent_ptr_type->data.pointer.is_volatile, PtrLenSingle, parent_ptr_type->data.pointer.is_const, parent_ptr_type->data.pointer.is_volatile, PtrLenSingle,
parent_ptr_align, 0, 0, parent_ptr_type->data.pointer.allow_zero); parent_ptr_align, 0, 0, parent_ptr_type->data.pointer.allow_zero);
@ -15141,6 +15228,20 @@ no_mem_slot:
return var_ptr_instruction; return var_ptr_instruction;
} }
// This function is called when a comptime value becomes accessible at runtime.
static void mark_comptime_value_escape(IrAnalyze *ira, IrInstruction *source_instr, ConstExprValue *val) {
ir_assert(value_is_comptime(val), source_instr);
if (val->special == ConstValSpecialUndef)
return;
if (val->type->id == ZigTypeIdFn && val->type->data.fn.fn_type_id.cc == CallingConventionUnspecified) {
ir_assert(val->data.x_ptr.special == ConstPtrSpecialFunction, source_instr);
if (val->data.x_ptr.data.fn.fn_entry->non_async_node == nullptr) {
val->data.x_ptr.data.fn.fn_entry->non_async_node = source_instr->source_node;
}
}
}
static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source_instr, static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source_instr,
IrInstruction *ptr, IrInstruction *uncasted_value, bool allow_write_through_const) IrInstruction *ptr, IrInstruction *uncasted_value, bool allow_write_through_const)
{ {
@ -15237,6 +15338,10 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source
break; break;
} }
if (instr_is_comptime(value)) {
mark_comptime_value_escape(ira, source_instr, &value->value);
}
IrInstructionStorePtr *store_ptr = ir_build_store_ptr(&ira->new_irb, source_instr->scope, IrInstructionStorePtr *store_ptr = ir_build_store_ptr(&ira->new_irb, source_instr->scope,
source_instr->source_node, ptr, value); source_instr->source_node, ptr, value);
return &store_ptr->base; return &store_ptr->base;
@ -15421,7 +15526,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c
IrInstruction *casted_new_stack = nullptr; IrInstruction *casted_new_stack = nullptr;
if (call_instruction->new_stack != nullptr) { if (call_instruction->new_stack != nullptr) {
ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8,
false, false, PtrLenUnknown, 0, 0, 0, false); false, false, PtrLenUnknown, target_fn_align(ira->codegen->zig_target), 0, 0, false);
ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr);
IrInstruction *new_stack = call_instruction->new_stack->child; IrInstruction *new_stack = call_instruction->new_stack->child;
if (type_is_invalid(new_stack->value.type)) if (type_is_invalid(new_stack->value.type))
@ -17123,7 +17228,7 @@ static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name,
buf_sprintf("dependency on dynamic library '%s' requires enabling Position Independent Code", buf_sprintf("dependency on dynamic library '%s' requires enabling Position Independent Code",
buf_ptr(lib_name))); buf_ptr(lib_name)));
add_error_note(ira->codegen, msg, source_node, add_error_note(ira->codegen, msg, source_node,
buf_sprintf("fixed by `--library %s` or `--enable-pic`", buf_ptr(lib_name))); buf_sprintf("fixed by `--library %s` or `-fPIC`", buf_ptr(lib_name)));
ira->codegen->reported_bad_link_libc_error = true; ira->codegen->reported_bad_link_libc_error = true;
} }
@ -20841,6 +20946,12 @@ static IrInstruction *ir_analyze_instruction_fence(IrAnalyze *ira, IrInstruction
if (!ir_resolve_atomic_order(ira, order_value, &order)) if (!ir_resolve_atomic_order(ira, order_value, &order))
return ira->codegen->invalid_instruction; return ira->codegen->invalid_instruction;
if (order < AtomicOrderAcquire) {
ir_add_error(ira, order_value,
buf_sprintf("atomic ordering must be Acquire or stricter"));
return ira->codegen->invalid_instruction;
}
IrInstruction *result = ir_build_fence(&ira->new_irb, IrInstruction *result = ir_build_fence(&ira->new_irb,
instruction->base.scope, instruction->base.source_node, order_value, order); instruction->base.scope, instruction->base.source_node, order_value, order);
result->value.type = ira->codegen->builtin_types.entry_void; result->value.type = ira->codegen->builtin_types.entry_void;
@ -24491,7 +24602,11 @@ static IrInstruction *ir_analyze_instruction_bit_cast_src(IrAnalyze *ira, IrInst
if (result_loc != nullptr && (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) if (result_loc != nullptr && (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc)))
return result_loc; return result_loc;
return instruction->result_loc_bit_cast->parent->gen_instruction; if (instruction->result_loc_bit_cast->parent->gen_instruction != nullptr) {
return instruction->result_loc_bit_cast->parent->gen_instruction;
}
return result_loc;
} }
static IrInstruction *ir_analyze_instruction_union_init_named_field(IrAnalyze *ira, static IrInstruction *ir_analyze_instruction_union_init_named_field(IrAnalyze *ira,

View File

@ -1755,7 +1755,7 @@ static void construct_linker_job_elf(LinkJob *lj) {
// libc dep // libc dep
if (g->libc_link_lib != nullptr) { if (g->libc_link_lib != nullptr && g->out_type != OutTypeObj) {
if (g->libc != nullptr) { if (g->libc != nullptr) {
if (!g->have_dynamic_link) { if (!g->have_dynamic_link) {
lj->args.append("--start-group"); lj->args.append("--start-group");

View File

@ -55,6 +55,7 @@ pub extern "c" fn fclose(stream: *FILE) c_int;
pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize;
pub extern "c" fn fread(ptr: [*]u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; pub extern "c" fn fread(ptr: [*]u8, size_of_type: usize, item_count: usize, stream: *FILE) usize;
pub extern "c" fn printf(format: [*]const u8, ...) c_int;
pub extern "c" fn abort() noreturn; pub extern "c" fn abort() noreturn;
pub extern "c" fn exit(code: c_int) noreturn; pub extern "c" fn exit(code: c_int) noreturn;
pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn isatty(fd: fd_t) c_int;
@ -64,10 +65,12 @@ pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int;
pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int;
pub extern "c" fn lseek(fd: fd_t, offset: isize, whence: c_int) isize; pub extern "c" fn lseek(fd: fd_t, offset: isize, whence: c_int) isize;
pub extern "c" fn open(path: [*]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn open(path: [*]const u8, oflag: c_uint, ...) c_int;
pub extern "c" fn openat(fd: c_int, path: [*]const u8, oflag: c_uint, ...) c_int;
pub extern "c" fn raise(sig: c_int) c_int; pub extern "c" fn raise(sig: c_int) c_int;
pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize; pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize;
pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: u64) isize; pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: u64) isize;
pub extern "c" fn preadv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint, offset: usize) isize; pub extern "c" fn preadv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint, offset: usize) isize;
pub extern "c" fn writev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint) isize;
pub extern "c" fn pwritev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint, offset: usize) isize; pub extern "c" fn pwritev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint, offset: usize) isize;
pub extern "c" fn stat(noalias path: [*]const u8, noalias buf: *Stat) c_int; pub extern "c" fn stat(noalias path: [*]const u8, noalias buf: *Stat) c_int;
pub extern "c" fn write(fd: fd_t, buf: [*]const u8, nbyte: usize) isize; pub extern "c" fn write(fd: fd_t, buf: [*]const u8, nbyte: usize) isize;
@ -112,7 +115,6 @@ pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, fl
pub extern "c" fn getsockopt(sockfd: fd_t, level: c_int, optname: c_int, optval: *c_void, optlen: *socklen_t) c_int; pub extern "c" fn getsockopt(sockfd: fd_t, level: c_int, optname: c_int, optval: *c_void, optlen: *socklen_t) c_int;
pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int; pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int;
pub extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize; pub extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize;
pub extern "c" fn openat(fd: c_int, path: [*]const u8, flags: c_int) c_int;
pub extern "c" fn setgid(ruid: c_uint, euid: c_uint) c_int; pub extern "c" fn setgid(ruid: c_uint, euid: c_uint) c_int;
pub extern "c" fn setuid(uid: c_uint) c_int; pub extern "c" fn setuid(uid: c_uint) c_int;
pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int; pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int;

View File

@ -6,4 +6,4 @@ pub const _errno = __error;
pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;
pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;

View File

@ -7,7 +7,7 @@ pub const _errno = __errno_location;
pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize)); pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize));
pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
pub extern "c" fn sched_getaffinity(pid: c_int, size: usize, set: *cpu_set_t) c_int; pub extern "c" fn sched_getaffinity(pid: c_int, size: usize, set: *cpu_set_t) c_int;
pub extern "c" fn eventfd(initval: c_uint, flags: c_uint) c_int; pub extern "c" fn eventfd(initval: c_uint, flags: c_uint) c_int;
pub extern "c" fn epoll_ctl(epfd: fd_t, op: c_uint, fd: fd_t, event: ?*epoll_event) c_int; pub extern "c" fn epoll_ctl(epfd: fd_t, op: c_uint, fd: fd_t, event: ?*epoll_event) c_int;

View File

@ -23,6 +23,7 @@ pub const Request = struct {
}; };
pub const Msg = union(enum) { pub const Msg = union(enum) {
WriteV: WriteV,
PWriteV: PWriteV, PWriteV: PWriteV,
PReadV: PReadV, PReadV: PReadV,
Open: Open, Open: Open,
@ -30,6 +31,14 @@ pub const Request = struct {
WriteFile: WriteFile, WriteFile: WriteFile,
End, // special - means the fs thread should exit End, // special - means the fs thread should exit
pub const WriteV = struct {
fd: fd_t,
iov: []const os.iovec_const,
result: Error!void,
pub const Error = os.WriteError;
};
pub const PWriteV = struct { pub const PWriteV = struct {
fd: fd_t, fd: fd_t,
iov: []const os.iovec_const, iov: []const os.iovec_const,
@ -77,7 +86,7 @@ pub const Request = struct {
pub const PWriteVError = error{OutOfMemory} || File.WriteError; pub const PWriteVError = error{OutOfMemory} || File.WriteError;
/// data - just the inner references - must live until pwritev frame completes. /// data - just the inner references - must live until pwritev frame completes.
pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
switch (builtin.os) { switch (builtin.os) {
.macosx, .macosx,
.linux, .linux,
@ -94,31 +103,31 @@ pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: us
}; };
} }
return await (async pwritevPosix(loop, fd, iovecs, offset) catch unreachable); return pwritevPosix(loop, fd, iovecs, offset);
}, },
.windows => { .windows => {
const data_copy = try std.mem.dupe(loop.allocator, []const u8, data); const data_copy = try std.mem.dupe(loop.allocator, []const u8, data);
defer loop.allocator.free(data_copy); defer loop.allocator.free(data_copy);
return await (async pwritevWindows(loop, fd, data, offset) catch unreachable); return pwritevWindows(loop, fd, data, offset);
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
/// data must outlive the returned frame /// data must outlive the returned frame
pub async fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { pub fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
if (data.len == 0) return; if (data.len == 0) return;
if (data.len == 1) return await (async pwriteWindows(loop, fd, data[0], offset) catch unreachable); if (data.len == 1) return pwriteWindows(loop, fd, data[0], offset);
// TODO do these in parallel // TODO do these in parallel
var off = offset; var off = offset;
for (data) |buf| { for (data) |buf| {
try await (async pwriteWindows(loop, fd, buf, off) catch unreachable); try pwriteWindows(loop, fd, buf, off);
off += buf.len; off += buf.len;
} }
} }
pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
var resume_node = Loop.ResumeNode.Basic{ var resume_node = Loop.ResumeNode.Basic{
.base = Loop.ResumeNode{ .base = Loop.ResumeNode{
.id = Loop.ResumeNode.Id.Basic, .id = Loop.ResumeNode.Id.Basic,
@ -158,7 +167,7 @@ pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64)
} }
/// iovecs must live until pwritev frame completes. /// iovecs must live until pwritev frame completes.
pub async fn pwritevPosix( pub fn pwritevPosix(
loop: *Loop, loop: *Loop,
fd: fd_t, fd: fd_t,
iovecs: []const os.iovec_const, iovecs: []const os.iovec_const,
@ -195,10 +204,44 @@ pub async fn pwritevPosix(
return req_node.data.msg.PWriteV.result; return req_node.data.msg.PWriteV.result;
} }
/// iovecs must live until pwritev frame completes.
pub fn writevPosix(
loop: *Loop,
fd: fd_t,
iovecs: []const os.iovec_const,
) os.WriteError!void {
var req_node = RequestNode{
.prev = null,
.next = null,
.data = Request{
.msg = Request.Msg{
.WriteV = Request.Msg.WriteV{
.fd = fd,
.iov = iovecs,
.result = undefined,
},
},
.finish = Request.Finish{
.TickNode = Loop.NextTickNode{
.prev = null,
.next = null,
.data = @frame(),
},
},
},
};
suspend {
loop.posixFsRequest(&req_node);
}
return req_node.data.msg.WriteV.result;
}
pub const PReadVError = error{OutOfMemory} || File.ReadError; pub const PReadVError = error{OutOfMemory} || File.ReadError;
/// data - just the inner references - must live until preadv frame completes. /// data - just the inner references - must live until preadv frame completes.
pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
assert(data.len != 0); assert(data.len != 0);
switch (builtin.os) { switch (builtin.os) {
.macosx, .macosx,
@ -216,21 +259,21 @@ pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PR
}; };
} }
return await (async preadvPosix(loop, fd, iovecs, offset) catch unreachable); return preadvPosix(loop, fd, iovecs, offset);
}, },
.windows => { .windows => {
const data_copy = try std.mem.dupe(loop.allocator, []u8, data); const data_copy = try std.mem.dupe(loop.allocator, []u8, data);
defer loop.allocator.free(data_copy); defer loop.allocator.free(data_copy);
return await (async preadvWindows(loop, fd, data_copy, offset) catch unreachable); return preadvWindows(loop, fd, data_copy, offset);
}, },
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
/// data must outlive the returned frame /// data must outlive the returned frame
pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize { pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize {
assert(data.len != 0); assert(data.len != 0);
if (data.len == 1) return await (async preadWindows(loop, fd, data[0], offset) catch unreachable); if (data.len == 1) return preadWindows(loop, fd, data[0], offset);
// TODO do these in parallel? // TODO do these in parallel?
var off: usize = 0; var off: usize = 0;
@ -238,7 +281,7 @@ pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u6
var inner_off: usize = 0; var inner_off: usize = 0;
while (true) { while (true) {
const v = data[iov_i]; const v = data[iov_i];
const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable); const amt_read = try preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off);
off += amt_read; off += amt_read;
inner_off += amt_read; inner_off += amt_read;
if (inner_off == v.len) { if (inner_off == v.len) {
@ -252,7 +295,7 @@ pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u6
} }
} }
pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize {
var resume_node = Loop.ResumeNode.Basic{ var resume_node = Loop.ResumeNode.Basic{
.base = Loop.ResumeNode{ .base = Loop.ResumeNode{
.id = Loop.ResumeNode.Id.Basic, .id = Loop.ResumeNode.Id.Basic,
@ -291,7 +334,7 @@ pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize
} }
/// iovecs must live until preadv frame completes /// iovecs must live until preadv frame completes
pub async fn preadvPosix( pub fn preadvPosix(
loop: *Loop, loop: *Loop,
fd: fd_t, fd: fd_t,
iovecs: []const os.iovec, iovecs: []const os.iovec,
@ -328,7 +371,7 @@ pub async fn preadvPosix(
return req_node.data.msg.PReadV.result; return req_node.data.msg.PReadV.result;
} }
pub async fn openPosix( pub fn openPosix(
loop: *Loop, loop: *Loop,
path: []const u8, path: []const u8,
flags: u32, flags: u32,
@ -367,11 +410,11 @@ pub async fn openPosix(
return req_node.data.msg.Open.result; return req_node.data.msg.Open.result;
} }
pub async fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t {
switch (builtin.os) { switch (builtin.os) {
.macosx, .linux, .freebsd, .netbsd => { .macosx, .linux, .freebsd, .netbsd => {
const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
return await (async openPosix(loop, path, flags, File.default_mode) catch unreachable); return openPosix(loop, path, flags, File.default_mode);
}, },
.windows => return windows.CreateFile( .windows => return windows.CreateFile(
@ -390,12 +433,12 @@ pub async fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t {
/// Creates if does not exist. Truncates the file if it exists. /// Creates if does not exist. Truncates the file if it exists.
/// Uses the default mode. /// Uses the default mode.
pub async fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t { pub fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t {
return await (async openWriteMode(loop, path, File.default_mode) catch unreachable); return openWriteMode(loop, path, File.default_mode);
} }
/// Creates if does not exist. Truncates the file if it exists. /// Creates if does not exist. Truncates the file if it exists.
pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t { pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t {
switch (builtin.os) { switch (builtin.os) {
.macosx, .macosx,
.linux, .linux,
@ -403,7 +446,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.
.netbsd, .netbsd,
=> { => {
const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
return await (async openPosix(loop, path, flags, File.default_mode) catch unreachable); return openPosix(loop, path, flags, File.default_mode);
}, },
.windows => return windows.CreateFile( .windows => return windows.CreateFile(
path, path,
@ -419,7 +462,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.
} }
/// Creates if does not exist. Does not truncate. /// Creates if does not exist. Does not truncate.
pub async fn openReadWrite( pub fn openReadWrite(
loop: *Loop, loop: *Loop,
path: []const u8, path: []const u8,
mode: File.Mode, mode: File.Mode,
@ -427,7 +470,7 @@ pub async fn openReadWrite(
switch (builtin.os) { switch (builtin.os) {
.macosx, .linux, .freebsd, .netbsd => { .macosx, .linux, .freebsd, .netbsd => {
const flags = os.O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC; const flags = os.O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
return await (async openPosix(loop, path, flags, mode) catch unreachable); return openPosix(loop, path, flags, mode);
}, },
.windows => return windows.CreateFile( .windows => return windows.CreateFile(
@ -576,24 +619,24 @@ pub const CloseOperation = struct {
/// contents must remain alive until writeFile completes. /// contents must remain alive until writeFile completes.
/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate /// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
pub async fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void { pub fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void {
return await (async writeFileMode(loop, path, contents, File.default_mode) catch unreachable); return writeFileMode(loop, path, contents, File.default_mode);
} }
/// contents must remain alive until writeFile completes. /// contents must remain alive until writeFile completes.
pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { pub fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void {
switch (builtin.os) { switch (builtin.os) {
.linux, .linux,
.macosx, .macosx,
.freebsd, .freebsd,
.netbsd, .netbsd,
=> return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable), => return writeFileModeThread(loop, path, contents, mode),
.windows => return await (async writeFileWindows(loop, path, contents) catch unreachable), .windows => return writeFileWindows(loop, path, contents),
else => @compileError("Unsupported OS"), else => @compileError("Unsupported OS"),
} }
} }
async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
const handle = try windows.CreateFile( const handle = try windows.CreateFile(
path, path,
windows.GENERIC_WRITE, windows.GENERIC_WRITE,
@ -605,10 +648,10 @@ async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !
); );
defer os.close(handle); defer os.close(handle);
try await (async pwriteWindows(loop, handle, contents, 0) catch unreachable); try pwriteWindows(loop, handle, contents, 0);
} }
async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void {
const path_with_null = try std.cstr.addNullByte(loop.allocator, path); const path_with_null = try std.cstr.addNullByte(loop.allocator, path);
defer loop.allocator.free(path_with_null); defer loop.allocator.free(path_with_null);
@ -646,11 +689,11 @@ async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8
/// The frame resumes when the last data has been confirmed written, but before the file handle /// The frame resumes when the last data has been confirmed written, but before the file handle
/// is closed. /// is closed.
/// Caller owns returned memory. /// Caller owns returned memory.
pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 { pub fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 {
var close_op = try CloseOperation.start(loop); var close_op = try CloseOperation.start(loop);
defer close_op.finish(); defer close_op.finish();
const fd = try await (async openRead(loop, file_path) catch unreachable); const fd = try openRead(loop, file_path);
close_op.setHandle(fd); close_op.setHandle(fd);
var list = std.ArrayList(u8).init(loop.allocator); var list = std.ArrayList(u8).init(loop.allocator);
@ -660,7 +703,7 @@ pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8
try list.ensureCapacity(list.len + mem.page_size); try list.ensureCapacity(list.len + mem.page_size);
const buf = list.items[list.len..]; const buf = list.items[list.len..];
const buf_array = [_][]u8{buf}; const buf_array = [_][]u8{buf};
const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable); const amt = try preadv(loop, fd, buf_array, list.len);
list.len += amt; list.len += amt;
if (list.len > max_size) { if (list.len > max_size) {
return error.FileTooBig; return error.FileTooBig;
@ -1273,11 +1316,11 @@ const test_tmp_dir = "std_event_fs_test";
// return result; // return result;
//} //}
async fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void { fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void {
result.* = await (async testFsWatch(loop) catch unreachable); result.* = testFsWatch(loop);
} }
async fn testFsWatch(loop: *Loop) !void { fn testFsWatch(loop: *Loop) !void {
const file_path = try std.fs.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" }); const file_path = try std.fs.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" });
defer loop.allocator.free(file_path); defer loop.allocator.free(file_path);
@ -1288,27 +1331,27 @@ async fn testFsWatch(loop: *Loop) !void {
const line2_offset = 7; const line2_offset = 7;
// first just write then read the file // first just write then read the file
try await try async writeFile(loop, file_path, contents); try writeFile(loop, file_path, contents);
const read_contents = try await try async readFile(loop, file_path, 1024 * 1024); const read_contents = try readFile(loop, file_path, 1024 * 1024);
testing.expectEqualSlices(u8, contents, read_contents); testing.expectEqualSlices(u8, contents, read_contents);
// now watch the file // now watch the file
var watch = try Watch(void).create(loop, 0); var watch = try Watch(void).create(loop, 0);
defer watch.destroy(); defer watch.destroy();
testing.expect((try await try async watch.addFile(file_path, {})) == null); testing.expect((try watch.addFile(file_path, {})) == null);
const ev = try async watch.channel.get(); const ev = async watch.channel.get();
var ev_consumed = false; var ev_consumed = false;
defer if (!ev_consumed) await ev; defer if (!ev_consumed) await ev;
// overwrite line 2 // overwrite line 2
const fd = try await try async openReadWrite(loop, file_path, File.default_mode); const fd = try await openReadWrite(loop, file_path, File.default_mode);
{ {
defer os.close(fd); defer os.close(fd);
try await try async pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset); try pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset);
} }
ev_consumed = true; ev_consumed = true;
@ -1316,7 +1359,7 @@ async fn testFsWatch(loop: *Loop) !void {
WatchEventId.CloseWrite => {}, WatchEventId.CloseWrite => {},
WatchEventId.Delete => @panic("wrong event"), WatchEventId.Delete => @panic("wrong event"),
} }
const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); const contents_updated = try readFile(loop, file_path, 1024 * 1024);
testing.expectEqualSlices(u8, testing.expectEqualSlices(u8,
\\line 1 \\line 1
\\lorem ipsum \\lorem ipsum

View File

@ -97,28 +97,27 @@ test "std.event.Future" {
loop.run(); loop.run();
} }
async fn testFuture(loop: *Loop) void { fn testFuture(loop: *Loop) void {
var future = Future(i32).init(loop); var future = Future(i32).init(loop);
var a = async waitOnFuture(&future); var a = async waitOnFuture(&future);
var b = async waitOnFuture(&future); var b = async waitOnFuture(&future);
var c = async resolveFuture(&future); resolveFuture(&future);
// TODO make this work: // TODO https://github.com/ziglang/zig/issues/3077
//const result = (await a) + (await b); //const result = (await a) + (await b);
const a_result = await a; const a_result = await a;
const b_result = await b; const b_result = await b;
const result = a_result + b_result; const result = a_result + b_result;
await c;
testing.expect(result == 12); testing.expect(result == 12);
} }
async fn waitOnFuture(future: *Future(i32)) i32 { fn waitOnFuture(future: *Future(i32)) i32 {
return future.get().*; return future.get().*;
} }
async fn resolveFuture(future: *Future(i32)) void { fn resolveFuture(future: *Future(i32)) void {
future.data = 6; future.data = 6;
future.resolve(); future.resolve();
} }

View File

@ -89,12 +89,15 @@ pub const Loop = struct {
pub const IoMode = enum { pub const IoMode = enum {
blocking, blocking,
evented, evented,
mixed,
}; };
pub const io_mode: IoMode = if (@hasDecl(root, "io_mode")) root.io_mode else IoMode.blocking; pub const io_mode: IoMode = if (@hasDecl(root, "io_mode")) root.io_mode else IoMode.blocking;
var global_instance_state: Loop = undefined; var global_instance_state: Loop = undefined;
threadlocal var per_thread_instance: ?*Loop = null;
const default_instance: ?*Loop = switch (io_mode) { const default_instance: ?*Loop = switch (io_mode) {
.blocking => null, .blocking => null,
.evented => &global_instance_state, .evented => &global_instance_state,
.mixed => per_thread_instance,
}; };
pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance; pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance;
@ -146,10 +149,12 @@ pub const Loop = struct {
.overlapped = ResumeNode.overlapped_init, .overlapped = ResumeNode.overlapped_init,
}, },
}; };
// We need at least one of these in case the fs thread wants to use onNextTick
const extra_thread_count = thread_count - 1; const extra_thread_count = thread_count - 1;
const resume_node_count = std.math.max(extra_thread_count, 1);
self.eventfd_resume_nodes = try self.allocator.alloc( self.eventfd_resume_nodes = try self.allocator.alloc(
std.atomic.Stack(ResumeNode.EventFd).Node, std.atomic.Stack(ResumeNode.EventFd).Node,
extra_thread_count, resume_node_count,
); );
errdefer self.allocator.free(self.eventfd_resume_nodes); errdefer self.allocator.free(self.eventfd_resume_nodes);
@ -194,7 +199,7 @@ pub const Loop = struct {
eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
.data = ResumeNode.EventFd{ .data = ResumeNode.EventFd{
.base = ResumeNode{ .base = ResumeNode{
.id = ResumeNode.Id.EventFd, .id = .EventFd,
.handle = undefined, .handle = undefined,
.overlapped = ResumeNode.overlapped_init, .overlapped = ResumeNode.overlapped_init,
}, },
@ -451,12 +456,12 @@ pub const Loop = struct {
self.finishOneEvent(); self.finishOneEvent();
} }
pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void { pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void {
defer self.linuxRemoveFd(fd); defer self.linuxRemoveFd(fd);
suspend { suspend {
var resume_node = ResumeNode.Basic{ var resume_node = ResumeNode.Basic{
.base = ResumeNode{ .base = ResumeNode{
.id = ResumeNode.Id.Basic, .id = .Basic,
.handle = @frame(), .handle = @frame(),
.overlapped = ResumeNode.overlapped_init, .overlapped = ResumeNode.overlapped_init,
}, },
@ -790,12 +795,15 @@ pub const Loop = struct {
fn posixFsRun(self: *Loop) void { fn posixFsRun(self: *Loop) void {
while (true) { while (true) {
if (builtin.os == builtin.Os.linux) { if (builtin.os == .linux) {
_ = @atomicRmw(i32, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); _ = @atomicRmw(i32, &self.os_data.fs_queue_item, .Xchg, 0, .SeqCst);
} }
while (self.os_data.fs_queue.get()) |node| { while (self.os_data.fs_queue.get()) |node| {
switch (node.data.msg) { switch (node.data.msg) {
.End => return, .End => return,
.WriteV => |*msg| {
msg.result = os.writev(msg.fd, msg.iov);
},
.PWriteV => |*msg| { .PWriteV => |*msg| {
msg.result = os.pwritev(msg.fd, msg.iov, msg.offset); msg.result = os.pwritev(msg.fd, msg.iov, msg.offset);
}, },
@ -827,14 +835,14 @@ pub const Loop = struct {
self.finishOneEvent(); self.finishOneEvent();
} }
switch (builtin.os) { switch (builtin.os) {
builtin.Os.linux => { .linux => {
const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null); const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null);
switch (os.linux.getErrno(rc)) { switch (os.linux.getErrno(rc)) {
0, os.EINTR, os.EAGAIN => continue, 0, os.EINTR, os.EAGAIN => continue,
else => unreachable, else => unreachable,
} }
}, },
builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => { .macosx, .freebsd, .netbsd => {
const fs_kevs = (*const [1]os.Kevent)(&self.os_data.fs_kevent_wait); const fs_kevs = (*const [1]os.Kevent)(&self.os_data.fs_kevent_wait);
var out_kevs: [1]os.Kevent = undefined; var out_kevs: [1]os.Kevent = undefined;
_ = os.kevent(self.os_data.fs_kqfd, fs_kevs, out_kevs[0..], null) catch unreachable; _ = os.kevent(self.os_data.fs_kqfd, fs_kevs, out_kevs[0..], null) catch unreachable;

View File

@ -69,7 +69,6 @@ pub fn format(
FormatFillAndAlign, FormatFillAndAlign,
FormatWidth, FormatWidth,
FormatPrecision, FormatPrecision,
Pointer,
}; };
comptime var start_index = 0; comptime var start_index = 0;
@ -109,9 +108,6 @@ pub fn format(
state = .Start; state = .Start;
start_index = i; start_index = i;
}, },
'*' => {
state = .Pointer;
},
':' => { ':' => {
state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth;
specifier_end = i; specifier_end = i;
@ -256,19 +252,6 @@ pub fn format(
@compileError("Unexpected character in precision value: " ++ [_]u8{c}); @compileError("Unexpected character in precision value: " ++ [_]u8{c});
}, },
}, },
.Pointer => switch (c) {
'}' => {
const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg);
try output(context, @typeName(@typeOf(args[arg_to_print]).Child));
try output(context, "@");
try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output);
state = .Start;
start_index = i + 1;
},
else => @compileError("Unexpected format character after '*'"),
},
} }
} }
comptime { comptime {
@ -293,12 +276,19 @@ pub fn format(
pub fn formatType( pub fn formatType(
value: var, value: var,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
max_depth: usize, max_depth: usize,
) Errors!void { ) Errors!void {
if (comptime std.mem.eql(u8, fmt, "*")) {
try output(context, @typeName(@typeOf(value).Child));
try output(context, "@");
try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output);
return;
}
const T = @typeOf(value); const T = @typeOf(value);
switch (@typeInfo(T)) { switch (@typeInfo(T)) {
.ComptimeInt, .Int, .Float => { .ComptimeInt, .Int, .Float => {
@ -438,15 +428,15 @@ pub fn formatType(
fn formatValue( fn formatValue(
value: var, value: var,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
) Errors!void { ) Errors!void {
if (comptime std.mem.eql(u8, fmt, "B")) { if (comptime std.mem.eql(u8, fmt, "B")) {
return formatBytes(value, options.width, 1000, context, Errors, output); return formatBytes(value, options, 1000, context, Errors, output);
} else if (comptime std.mem.eql(u8, fmt, "Bi")) { } else if (comptime std.mem.eql(u8, fmt, "Bi")) {
return formatBytes(value, options.width, 1024, context, Errors, output); return formatBytes(value, options, 1024, context, Errors, output);
} }
const T = @typeOf(value); const T = @typeOf(value);
@ -460,7 +450,7 @@ fn formatValue(
pub fn formatIntValue( pub fn formatIntValue(
value: var, value: var,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -479,7 +469,7 @@ pub fn formatIntValue(
uppercase = false; uppercase = false;
} else if (comptime std.mem.eql(u8, fmt, "c")) { } else if (comptime std.mem.eql(u8, fmt, "c")) {
if (@typeOf(int_value).bit_count <= 8) { if (@typeOf(int_value).bit_count <= 8) {
return formatAsciiChar(u8(int_value), context, Errors, output); return formatAsciiChar(u8(int_value), options, context, Errors, output);
} else { } else {
@compileError("Cannot print integer that is larger than 8 bits as a ascii"); @compileError("Cannot print integer that is larger than 8 bits as a ascii");
} }
@ -496,21 +486,21 @@ pub fn formatIntValue(
@compileError("Unknown format string: '" ++ fmt ++ "'"); @compileError("Unknown format string: '" ++ fmt ++ "'");
} }
return formatInt(int_value, radix, uppercase, options.width orelse 0, context, Errors, output); return formatInt(int_value, radix, uppercase, options, context, Errors, output);
} }
fn formatFloatValue( fn formatFloatValue(
value: var, value: var,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
) Errors!void { ) Errors!void {
if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
return formatFloatScientific(value, options.precision, context, Errors, output); return formatFloatScientific(value, options, context, Errors, output);
} else if (comptime std.mem.eql(u8, fmt, "d")) { } else if (comptime std.mem.eql(u8, fmt, "d")) {
return formatFloatDecimal(value, options.precision, context, Errors, output); return formatFloatDecimal(value, options, context, Errors, output);
} else { } else {
@compileError("Unknown format string: '" ++ fmt ++ "'"); @compileError("Unknown format string: '" ++ fmt ++ "'");
} }
@ -519,7 +509,7 @@ fn formatFloatValue(
pub fn formatText( pub fn formatText(
bytes: []const u8, bytes: []const u8,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -527,11 +517,10 @@ pub fn formatText(
if (fmt.len == 0) { if (fmt.len == 0) {
return output(context, bytes); return output(context, bytes);
} else if (comptime std.mem.eql(u8, fmt, "s")) { } else if (comptime std.mem.eql(u8, fmt, "s")) {
if (options.width) |w| return formatBuf(bytes, w, context, Errors, output); return formatBuf(bytes, options, context, Errors, output);
return formatBuf(bytes, 0, context, Errors, output);
} else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) {
for (bytes) |c| { for (bytes) |c| {
try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, context, Errors, output);
} }
return; return;
} else { } else {
@ -541,6 +530,7 @@ pub fn formatText(
pub fn formatAsciiChar( pub fn formatAsciiChar(
c: u8, c: u8,
options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -550,15 +540,16 @@ pub fn formatAsciiChar(
pub fn formatBuf( pub fn formatBuf(
buf: []const u8, buf: []const u8,
width: usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
) Errors!void { ) Errors!void {
try output(context, buf); try output(context, buf);
const width = options.width orelse 0;
var leftover_padding = if (width > buf.len) (width - buf.len) else return; var leftover_padding = if (width > buf.len) (width - buf.len) else return;
const pad_byte: u8 = ' '; const pad_byte: u8 = options.fill;
while (leftover_padding > 0) : (leftover_padding -= 1) { while (leftover_padding > 0) : (leftover_padding -= 1) {
try output(context, (*const [1]u8)(&pad_byte)[0..1]); try output(context, (*const [1]u8)(&pad_byte)[0..1]);
} }
@ -569,7 +560,7 @@ pub fn formatBuf(
// same type unambiguously. // same type unambiguously.
pub fn formatFloatScientific( pub fn formatFloatScientific(
value: var, value: var,
maybe_precision: ?usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -591,7 +582,7 @@ pub fn formatFloatScientific(
if (x == 0.0) { if (x == 0.0) {
try output(context, "0"); try output(context, "0");
if (maybe_precision) |precision| { if (options.precision) |precision| {
if (precision != 0) { if (precision != 0) {
try output(context, "."); try output(context, ".");
var i: usize = 0; var i: usize = 0;
@ -610,7 +601,7 @@ pub fn formatFloatScientific(
var buffer: [32]u8 = undefined; var buffer: [32]u8 = undefined;
var float_decimal = errol.errol3(x, buffer[0..]); var float_decimal = errol.errol3(x, buffer[0..]);
if (maybe_precision) |precision| { if (options.precision) |precision| {
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
try output(context, float_decimal.digits[0..1]); try output(context, float_decimal.digits[0..1]);
@ -650,13 +641,13 @@ pub fn formatFloatScientific(
if (exp > -10 and exp < 10) { if (exp > -10 and exp < 10) {
try output(context, "0"); try output(context, "0");
} }
try formatInt(exp, 10, false, 0, context, Errors, output); try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output);
} else { } else {
try output(context, "-"); try output(context, "-");
if (exp > -10 and exp < 10) { if (exp > -10 and exp < 10) {
try output(context, "0"); try output(context, "0");
} }
try formatInt(-exp, 10, false, 0, context, Errors, output); try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output);
} }
} }
@ -664,7 +655,7 @@ pub fn formatFloatScientific(
// By default floats are printed at full precision (no rounding). // By default floats are printed at full precision (no rounding).
pub fn formatFloatDecimal( pub fn formatFloatDecimal(
value: var, value: var,
maybe_precision: ?usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -686,7 +677,7 @@ pub fn formatFloatDecimal(
if (x == 0.0) { if (x == 0.0) {
try output(context, "0"); try output(context, "0");
if (maybe_precision) |precision| { if (options.precision) |precision| {
if (precision != 0) { if (precision != 0) {
try output(context, "."); try output(context, ".");
var i: usize = 0; var i: usize = 0;
@ -707,7 +698,7 @@ pub fn formatFloatDecimal(
var buffer: [32]u8 = undefined; var buffer: [32]u8 = undefined;
var float_decimal = errol.errol3(x, buffer[0..]); var float_decimal = errol.errol3(x, buffer[0..]);
if (maybe_precision) |precision| { if (options.precision) |precision| {
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
// exp < 0 means the leading is always 0 as errol result is normalized. // exp < 0 means the leading is always 0 as errol result is normalized.
@ -809,7 +800,7 @@ pub fn formatFloatDecimal(
pub fn formatBytes( pub fn formatBytes(
value: var, value: var,
width: ?usize, options: FormatOptions,
comptime radix: usize, comptime radix: usize,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
@ -833,7 +824,7 @@ pub fn formatBytes(
else => unreachable, else => unreachable,
}; };
try formatFloatDecimal(new_value, width, context, Errors, output); try formatFloatDecimal(new_value, options, context, Errors, output);
if (suffix == ' ') { if (suffix == ' ') {
return output(context, "B"); return output(context, "B");
@ -851,7 +842,7 @@ pub fn formatInt(
value: var, value: var,
base: u8, base: u8,
uppercase: bool, uppercase: bool,
width: usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -863,9 +854,9 @@ pub fn formatInt(
value; value;
if (@typeOf(int_value).is_signed) { if (@typeOf(int_value).is_signed) {
return formatIntSigned(int_value, base, uppercase, width, context, Errors, output); return formatIntSigned(int_value, base, uppercase, options, context, Errors, output);
} else { } else {
return formatIntUnsigned(int_value, base, uppercase, width, context, Errors, output); return formatIntUnsigned(int_value, base, uppercase, options, context, Errors, output);
} }
} }
@ -873,26 +864,30 @@ fn formatIntSigned(
value: var, value: var,
base: u8, base: u8,
uppercase: bool, uppercase: bool,
width: usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
) Errors!void { ) Errors!void {
const new_options = FormatOptions{
.width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
.precision = options.precision,
.fill = options.fill,
};
const uint = @IntType(false, @typeOf(value).bit_count); const uint = @IntType(false, @typeOf(value).bit_count);
if (value < 0) { if (value < 0) {
const minus_sign: u8 = '-'; const minus_sign: u8 = '-';
try output(context, (*const [1]u8)(&minus_sign)[0..]); try output(context, (*const [1]u8)(&minus_sign)[0..]);
const new_value = @intCast(uint, -(value + 1)) + 1; const new_value = @intCast(uint, -(value + 1)) + 1;
const new_width = if (width == 0) 0 else (width - 1); return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output);
return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output); } else if (options.width == null or options.width.? == 0) {
} else if (width == 0) { return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, context, Errors, output);
return formatIntUnsigned(@intCast(uint, value), base, uppercase, width, context, Errors, output);
} else { } else {
const plus_sign: u8 = '+'; const plus_sign: u8 = '+';
try output(context, (*const [1]u8)(&plus_sign)[0..]); try output(context, (*const [1]u8)(&plus_sign)[0..]);
const new_value = @intCast(uint, value); const new_value = @intCast(uint, value);
const new_width = if (width == 0) 0 else (width - 1); return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output);
return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output);
} }
} }
@ -900,7 +895,7 @@ fn formatIntUnsigned(
value: var, value: var,
base: u8, base: u8,
uppercase: bool, uppercase: bool,
width: usize, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -921,31 +916,32 @@ fn formatIntUnsigned(
} }
const digits_buf = buf[index..]; const digits_buf = buf[index..];
const width = options.width orelse 0;
const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
if (padding > index) { if (padding > index) {
const zero_byte: u8 = '0'; const zero_byte: u8 = options.fill;
var leftover_padding = padding - index; var leftover_padding = padding - index;
while (true) { while (true) {
try output(context, (*const [1]u8)(&zero_byte)[0..]); try output(context, (*const [1]u8)(&zero_byte)[0..]);
leftover_padding -= 1; leftover_padding -= 1;
if (leftover_padding == 0) break; if (leftover_padding == 0) break;
} }
mem.set(u8, buf[0..index], '0'); mem.set(u8, buf[0..index], options.fill);
return output(context, buf); return output(context, buf);
} else { } else {
const padded_buf = buf[index - padding ..]; const padded_buf = buf[index - padding ..];
mem.set(u8, padded_buf[0..padding], '0'); mem.set(u8, padded_buf[0..padding], options.fill);
return output(context, padded_buf); return output(context, padded_buf);
} }
} }
pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width: usize) usize { pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize {
var context = FormatIntBuf{ var context = FormatIntBuf{
.out_buf = out_buf, .out_buf = out_buf,
.index = 0, .index = 0,
}; };
formatInt(value, base, uppercase, width, &context, error{}, formatIntCallback) catch unreachable; formatInt(value, base, uppercase, options, &context, error{}, formatIntCallback) catch unreachable;
return context.index; return context.index;
} }
const FormatIntBuf = struct { const FormatIntBuf = struct {
@ -1088,23 +1084,23 @@ fn countSize(size: *usize, bytes: []const u8) (error{}!void) {
test "bufPrintInt" { test "bufPrintInt" {
var buffer: [100]u8 = undefined; var buffer: [100]u8 = undefined;
const buf = buffer[0..]; const buf = buffer[0..];
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, FormatOptions{}), "-101111000110000101001110"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, 0), "-12345678")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, FormatOptions{}), "-12345678"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, 0), "-bc614e")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, FormatOptions{}), "-bc614e"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, 0), "-BC614E")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, FormatOptions{}), "-BC614E"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, 0), "12345678")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, FormatOptions{}), "12345678"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, 6), "000666")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, FormatOptions{ .width = 6 }), " 666"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 6), "001234")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, FormatOptions{ .width = 6 }), " 1234"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 1), "1234")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, FormatOptions{ .width = 1 }), "1234"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, 3), "+42")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, FormatOptions{ .width = 3 }), "+42"));
testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, 3), "-42")); testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, FormatOptions{ .width = 3 }), "-42"));
} }
fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: usize) []u8 { fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 {
return buf[0..formatIntBuf(buf, value, base, uppercase, width)]; return buf[0..formatIntBuf(buf, value, base, uppercase, options)];
} }
test "parse u64 digit too big" { test "parse u64 digit too big" {
@ -1162,7 +1158,8 @@ test "int.specifier" {
} }
test "int.padded" { test "int.padded" {
try testFmt("u8: '0001'", "u8: '{:4}'", u8(1)); try testFmt("u8: ' 1'", "u8: '{:4}'", u8(1));
try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", u8(1));
} }
test "buffer" { test "buffer" {
@ -1237,7 +1234,7 @@ test "cstr" {
test "filesize" { test "filesize" {
try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024));
try testFmt("file size: 66.06MB\n", "file size: {B:2}\n", usize(63 * 1024 * 1024)); try testFmt("file size: 66.06MB\n", "file size: {B:.2}\n", usize(63 * 1024 * 1024));
} }
test "struct" { test "struct" {
@ -1342,7 +1339,7 @@ test "custom" {
pub fn format( pub fn format(
self: SelfType, self: SelfType,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,
@ -1548,7 +1545,7 @@ test "formatType max_depth" {
pub fn format( pub fn format(
self: SelfType, self: SelfType,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: FormatOptions, options: FormatOptions,
context: var, context: var,
comptime Errors: type, comptime Errors: type,
output: fn (@typeOf(context), []const u8) Errors!void, output: fn (@typeOf(context), []const u8) Errors!void,

View File

@ -442,6 +442,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
} }
} }
/// TODO: separate this API into the one that opens directory handles to then subsequently open
/// files, and into the one that reads files from an open directory handle.
pub const Dir = struct { pub const Dir = struct {
handle: Handle, handle: Handle,
allocator: *Allocator, allocator: *Allocator,
@ -564,6 +566,17 @@ pub const Dir = struct {
} }
} }
pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File {
const path_c = try os.toPosixPath(file_path);
return self.openReadC(&path_c);
}
pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File {
const flags = os.O_LARGEFILE | os.O_RDONLY;
const fd = try os.openatC(self.handle.fd, file_path, flags, 0);
return File.openHandle(fd);
}
fn nextDarwin(self: *Dir) !?Entry { fn nextDarwin(self: *Dir) !?Entry {
start_over: while (true) { start_over: while (true) {
if (self.handle.index >= self.handle.end_index) { if (self.handle.index >= self.handle.end_index) {

View File

@ -302,6 +302,14 @@ pub const File = struct {
return os.write(self.handle, bytes); return os.write(self.handle, bytes);
} }
pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {
if (std.event.Loop.instance) |loop| {
return std.event.fs.writevPosix(loop, self.handle, iovecs);
} else {
return os.writev(self.handle, iovecs);
}
}
pub fn inStream(file: File) InStream { pub fn inStream(file: File) InStream {
return InStream{ return InStream{
.file = file, .file = file,

View File

@ -146,7 +146,7 @@ pub fn InStream(comptime ReadError: type) type {
/// Same as `readFull` but end of stream returns `error.EndOfStream`. /// Same as `readFull` but end of stream returns `error.EndOfStream`.
pub fn readNoEof(self: *Self, buf: []u8) !void { pub fn readNoEof(self: *Self, buf: []u8) !void {
const amt_read = try self.read(buf); const amt_read = try self.readFull(buf);
if (amt_read < buf.len) return error.EndOfStream; if (amt_read < buf.len) return error.EndOfStream;
} }

View File

@ -519,7 +519,7 @@ pub const Int = struct {
pub fn format( pub fn format(
self: Int, self: Int,
comptime fmt: []const u8, comptime fmt: []const u8,
comptime options: std.fmt.FormatOptions, options: std.fmt.FormatOptions,
context: var, context: var,
comptime FmtError: type, comptime FmtError: type,
output: fn (@typeOf(context), []const u8) FmtError!void, output: fn (@typeOf(context), []const u8) FmtError!void,

View File

@ -215,3 +215,33 @@ test "std.net.parseIp6" {
assert(addr.addr[1] == 0x01); assert(addr.addr[1] == 0x01);
assert(addr.addr[2] == 0x00); assert(addr.addr[2] == 0x00);
} }
pub fn connectUnixSocket(path: []const u8) !std.fs.File {
const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0;
const sockfd = try os.socket(
os.AF_UNIX,
os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
0,
);
errdefer os.close(sockfd);
var sock_addr = os.sockaddr{
.un = os.sockaddr_un{
.family = os.AF_UNIX,
.path = undefined,
},
};
if (path.len > @typeOf(sock_addr.un.path).len) return error.NameTooLong;
mem.copy(u8, sock_addr.un.path[0..], path);
const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len);
if (std.event.Loop.instance) |loop| {
try os.connect_async(sockfd, &sock_addr, size);
try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET);
try os.getsockoptError(sockfd);
} else {
try os.connect(sockfd, &sock_addr, size);
}
return std.fs.File.openHandle(sockfd);
}

View File

@ -99,19 +99,30 @@ pub const GetRandomError = OpenError;
/// When linking against libc, this calls the /// When linking against libc, this calls the
/// appropriate OS-specific library call. Otherwise it uses the zig standard /// appropriate OS-specific library call. Otherwise it uses the zig standard
/// library implementation. /// library implementation.
pub fn getrandom(buf: []u8) GetRandomError!void { pub fn getrandom(buffer: []u8) GetRandomError!void {
if (windows.is_the_target) { if (windows.is_the_target) {
return windows.RtlGenRandom(buf); return windows.RtlGenRandom(buffer);
} }
if (linux.is_the_target) { if (linux.is_the_target or freebsd.is_the_target) {
while (true) { var buf = buffer;
const err = if (std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok) blk: { const use_c = !linux.is_the_target or
break :blk errno(std.c.getrandom(buf.ptr, buf.len, 0)); std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok;
while (buf.len != 0) {
var err: u16 = undefined;
const num_read = if (use_c) blk: {
const rc = std.c.getrandom(buf.ptr, buf.len, 0);
err = std.c.getErrno(rc);
break :blk @bitCast(usize, rc);
} else blk: { } else blk: {
break :blk linux.getErrno(linux.getrandom(buf.ptr, buf.len, 0)); const rc = linux.getrandom(buf.ptr, buf.len, 0);
err = linux.getErrno(rc);
break :blk rc;
}; };
switch (err) { switch (err) {
0 => return, 0 => buf = buf[num_read..],
EINVAL => unreachable, EINVAL => unreachable,
EFAULT => unreachable, EFAULT => unreachable,
EINTR => continue, EINTR => continue,
@ -119,27 +130,15 @@ pub fn getrandom(buf: []u8) GetRandomError!void {
else => return unexpectedErrno(err), else => return unexpectedErrno(err),
} }
} }
} return;
if (freebsd.is_the_target) {
while (true) {
const err = std.c.getErrno(std.c.getrandom(buf.ptr, buf.len, 0));
switch (err) {
0 => return,
EINVAL => unreachable,
EFAULT => unreachable,
EINTR => continue,
else => return unexpectedErrno(err),
}
}
} }
if (wasi.is_the_target) { if (wasi.is_the_target) {
switch (wasi.random_get(buf.ptr, buf.len)) { switch (wasi.random_get(buffer.ptr, buffer.len)) {
0 => return, 0 => return,
else => |err| return unexpectedErrno(err), else => |err| return unexpectedErrno(err),
} }
} }
return getRandomBytesDevURandom(buf); return getRandomBytesDevURandom(buffer);
} }
fn getRandomBytesDevURandom(buf: []u8) !void { fn getRandomBytesDevURandom(buf: []u8) !void {
@ -440,6 +439,33 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
/// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted. /// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted.
/// This function is for blocking file descriptors only. For non-blocking, see /// This function is for blocking file descriptors only. For non-blocking, see
/// `writevAsync`.
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
while (true) {
// TODO handle the case when iov_len is too large and get rid of this @intCast
const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len));
switch (errno(rc)) {
0 => return,
EINTR => continue,
EINVAL => unreachable,
EFAULT => unreachable,
EAGAIN => unreachable, // This function is for blocking writes.
EBADF => unreachable, // Always a race condition.
EDESTADDRREQ => unreachable, // `connect` was never called.
EDQUOT => return error.DiskQuota,
EFBIG => return error.FileTooBig,
EIO => return error.InputOutput,
ENOSPC => return error.NoSpaceLeft,
EPERM => return error.AccessDenied,
EPIPE => return error.BrokenPipe,
else => |err| return unexpectedErrno(err),
}
}
}
/// Write multiple buffers to a file descriptor, with a position offset.
/// Keeps trying if it gets interrupted.
/// This function is for blocking file descriptors only. For non-blocking, see
/// `pwritevAsync`. /// `pwritevAsync`.
pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
if (darwin.is_the_target) { if (darwin.is_the_target) {
@ -524,7 +550,6 @@ pub const OpenError = error{
}; };
/// Open and possibly create a file. Keeps trying if it gets interrupted. /// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` needs to be copied in memory to add a null terminating byte.
/// See also `openC`. /// See also `openC`.
pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t { pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t {
const file_path_c = try toPosixPath(file_path); const file_path_c = try toPosixPath(file_path);
@ -564,6 +589,47 @@ pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t {
} }
} }
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openatC`.
pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {
const file_path_c = try toPosixPath(file_path);
return openatC(dir_fd, &file_path_c, flags, mode);
}
/// Open and possibly create a file. Keeps trying if it gets interrupted.
/// `file_path` is relative to the open directory handle `dir_fd`.
/// See also `openat`.
pub fn openatC(dir_fd: fd_t, file_path: [*]const u8, flags: u32, mode: usize) OpenError!fd_t {
while (true) {
const rc = system.openat(dir_fd, file_path, flags, mode);
switch (errno(rc)) {
0 => return @intCast(fd_t, rc),
EINTR => continue,
EFAULT => unreachable,
EINVAL => unreachable,
EACCES => return error.AccessDenied,
EFBIG => return error.FileTooBig,
EOVERFLOW => return error.FileTooBig,
EISDIR => return error.IsDir,
ELOOP => return error.SymLinkLoop,
EMFILE => return error.ProcessFdQuotaExceeded,
ENAMETOOLONG => return error.NameTooLong,
ENFILE => return error.SystemFdQuotaExceeded,
ENODEV => return error.NoDevice,
ENOENT => return error.FileNotFound,
ENOMEM => return error.SystemResources,
ENOSPC => return error.NoSpaceLeft,
ENOTDIR => return error.NotDir,
EPERM => return error.AccessDenied,
EEXIST => return error.PathAlreadyExists,
EBUSY => return error.DeviceBusy,
else => |err| return unexpectedErrno(err),
}
}
}
pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void {
while (true) { while (true) {
switch (errno(system.dup2(old_fd, new_fd))) { switch (errno(system.dup2(old_fd, new_fd))) {
@ -1655,7 +1721,7 @@ pub const ConnectError = error{
/// For non-blocking, see `connect_async`. /// For non-blocking, see `connect_async`.
pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void { pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
while (true) { while (true) {
switch (errno(system.connect(sockfd, sock_addr, @sizeOf(sockaddr)))) { switch (errno(system.connect(sockfd, sock_addr, len))) {
0 => return, 0 => return,
EACCES => return error.PermissionDenied, EACCES => return error.PermissionDenied,
EPERM => return error.PermissionDenied, EPERM => return error.PermissionDenied,
@ -1683,7 +1749,8 @@ pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!v
/// It expects to receive EINPROGRESS`. /// It expects to receive EINPROGRESS`.
pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void { pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void {
while (true) { while (true) {
switch (errno(system.connect(sockfd, sock_addr, @sizeOf(sockaddr)))) { switch (errno(system.connect(sockfd, sock_addr, len))) {
EINVAL => unreachable,
EINTR => continue, EINTR => continue,
0, EINPROGRESS => return, 0, EINPROGRESS => return,
EACCES => return error.PermissionDenied, EACCES => return error.PermissionDenied,

View File

@ -784,6 +784,7 @@ pub const socklen_t = u32;
pub const sockaddr = extern union { pub const sockaddr = extern union {
in: sockaddr_in, in: sockaddr_in,
in6: sockaddr_in6, in6: sockaddr_in6,
un: sockaddr_un,
}; };
pub const sockaddr_in = extern struct { pub const sockaddr_in = extern struct {

View File

@ -124,7 +124,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream; \\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream;
\\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable;
\\} \\}
, "Hello, world!\n0012 012 a\n"); , "Hello, world!\n 12 12 a\n");
cases.addC("number literals", cases.addC("number literals",
\\const builtin = @import("builtin"); \\const builtin = @import("builtin");

View File

@ -2,6 +2,107 @@ const tests = @import("tests.zig");
const builtin = @import("builtin"); const builtin = @import("builtin");
pub fn addCases(cases: *tests.CompileErrorContext) void { pub fn addCases(cases: *tests.CompileErrorContext) void {
cases.addCase(x: {
var tc = cases.create("variable in inline assembly template cannot be found",
\\export fn entry() void {
\\ var sp = asm volatile (
\\ "mov %[foo], sp"
\\ : [bar] "=r" (-> usize)
\\ );
\\}
, "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs.");
tc.target = tests.Target{
.Cross = tests.CrossTarget{
.arch = .x86_64,
.os = .linux,
.abi = .gnu,
},
};
break :x tc;
});
cases.add(
"indirect recursion of async functions detected",
\\var frame: ?anyframe = null;
\\
\\export fn a() void {
\\ _ = async rangeSum(10);
\\ while (frame) |f| resume f;
\\}
\\
\\fn rangeSum(x: i32) i32 {
\\ suspend {
\\ frame = @frame();
\\ }
\\ frame = null;
\\
\\ if (x == 0) return 0;
\\ var child = rangeSumIndirect(x - 1);
\\ return child + 1;
\\}
\\
\\fn rangeSumIndirect(x: i32) i32 {
\\ suspend {
\\ frame = @frame();
\\ }
\\ frame = null;
\\
\\ if (x == 0) return 0;
\\ var child = rangeSum(x - 1);
\\ return child + 1;
\\}
,
"tmp.zig:8:1: error: '@Frame(rangeSum)' depends on itself",
"tmp.zig:15:33: note: when analyzing type '@Frame(rangeSumIndirect)' here",
"tmp.zig:26:25: note: when analyzing type '@Frame(rangeSum)' here",
);
cases.add(
"non-async function pointer eventually is inferred to become async",
\\export fn a() void {
\\ var non_async_fn: fn () void = undefined;
\\ non_async_fn = func;
\\}
\\fn func() void {
\\ suspend;
\\}
,
"tmp.zig:5:1: error: 'func' cannot be async",
"tmp.zig:3:20: note: required to be non-async here",
"tmp.zig:6:5: note: suspends here",
);
cases.add(
"bad alignment in @asyncCall",
\\export fn entry() void {
\\ var ptr: async fn () void = func;
\\ var bytes: [64]u8 = undefined;
\\ _ = @asyncCall(&bytes, {}, ptr);
\\}
\\async fn func() void {}
,
"tmp.zig:4:21: error: expected type '[]align(16) u8', found '*[64]u8'",
);
cases.add(
"atomic orderings of fence Acquire or stricter",
\\export fn entry() void {
\\ @fence(.Monotonic);
\\}
,
"tmp.zig:2:12: error: atomic ordering must be Acquire or stricter",
);
cases.add(
"bad alignment in implicit cast from array pointer to slice",
\\export fn a() void {
\\ var x: [10]u8 = undefined;
\\ var y: []align(16) u8 = &x;
\\}
,
"tmp.zig:3:30: error: expected type '[]align(16) u8', found '*[10]u8'",
);
cases.add( cases.add(
"result location incompatibility mismatching handle_is_ptr (generic call)", "result location incompatibility mismatching handle_is_ptr (generic call)",
\\export fn entry() void { \\export fn entry() void {
@ -164,7 +265,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
"non async function pointer passed to @asyncCall", "non async function pointer passed to @asyncCall",
\\export fn entry() void { \\export fn entry() void {
\\ var ptr = afunc; \\ var ptr = afunc;
\\ var bytes: [100]u8 = undefined; \\ var bytes: [100]u8 align(16) = undefined;
\\ _ = @asyncCall(&bytes, {}, ptr); \\ _ = @asyncCall(&bytes, {}, ptr);
\\} \\}
\\fn afunc() void { } \\fn afunc() void { }

View File

@ -30,7 +30,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void {
\\ @import("std").os.exit(126); \\ @import("std").os.exit(126);
\\} \\}
\\pub fn main() void { \\pub fn main() void {
\\ var bytes: [1]u8 = undefined; \\ var bytes: [1]u8 align(16) = undefined;
\\ var ptr = other; \\ var ptr = other;
\\ var frame = @asyncCall(&bytes, {}, ptr); \\ var frame = @asyncCall(&bytes, {}, ptr);
\\} \\}

View File

@ -1,5 +1,6 @@
const expect = @import("std").testing.expect; const std = @import("std");
const mem = @import("std").mem; const expect = std.testing.expect;
const mem = std.mem;
test "arrays" { test "arrays" {
var array: [5]u32 = undefined; var array: [5]u32 = undefined;
@ -274,3 +275,20 @@ test "double nested array to const slice cast in array literal" {
S.entry(2); S.entry(2);
comptime S.entry(2); comptime S.entry(2);
} }
test "read/write through global variable array of struct fields initialized via array mult" {
const S = struct {
fn doTheTest() void {
expect(storage[0].term == 1);
storage[0] = MyStruct{ .term = 123 };
expect(storage[0].term == 123);
}
pub const MyStruct = struct {
term: usize,
};
var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
};
S.doTheTest();
}

View File

@ -280,7 +280,7 @@ test "async fn pointer in a struct field" {
bar: async fn (*i32) void, bar: async fn (*i32) void,
}; };
var foo = Foo{ .bar = simpleAsyncFn2 }; var foo = Foo{ .bar = simpleAsyncFn2 };
var bytes: [64]u8 = undefined; var bytes: [64]u8 align(16) = undefined;
const f = @asyncCall(&bytes, {}, foo.bar, &data); const f = @asyncCall(&bytes, {}, foo.bar, &data);
comptime expect(@typeOf(f) == anyframe->void); comptime expect(@typeOf(f) == anyframe->void);
expect(data == 2); expect(data == 2);
@ -317,7 +317,7 @@ test "@asyncCall with return type" {
} }
}; };
var foo = Foo{ .bar = Foo.middle }; var foo = Foo{ .bar = Foo.middle };
var bytes: [150]u8 = undefined; var bytes: [150]u8 align(16) = undefined;
var aresult: i32 = 0; var aresult: i32 = 0;
_ = @asyncCall(&bytes, &aresult, foo.bar); _ = @asyncCall(&bytes, &aresult, foo.bar);
expect(aresult == 0); expect(aresult == 0);
@ -817,3 +817,30 @@ test "struct parameter to async function is copied to the frame" {
}; };
S.doTheTest(); S.doTheTest();
} }
test "cast fn to async fn when it is inferred to be async" {
const S = struct {
var frame: anyframe = undefined;
var ok = false;
fn doTheTest() void {
var ptr: async fn () i32 = undefined;
ptr = func;
var buf: [100]u8 align(16) = undefined;
var result: i32 = undefined;
_ = await @asyncCall(&buf, &result, ptr);
expect(result == 1234);
ok = true;
}
fn func() i32 {
suspend {
frame = @frame();
}
return 1234;
}
};
_ = async S.doTheTest();
resume S.frame;
expect(S.ok);
}

View File

@ -125,3 +125,17 @@ test "implicit cast to error union by returning" {
S.entry(); S.entry();
comptime S.entry(); comptime S.entry();
} }
// issue #3010: compiler segfault
test "bitcast literal [4]u8 param to u32" {
const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 });
expect(ip == maxInt(u32));
}
test "bitcast packed struct literal to byte" {
const Foo = packed struct {
value: u8,
};
const casted = @bitCast(u8, Foo{ .value = 0xF });
expect(casted == 0xf);
}

View File

@ -8,8 +8,8 @@ const ET = union(enum) {
pub fn print(a: *const ET, buf: []u8) anyerror!usize { pub fn print(a: *const ET, buf: []u8) anyerror!usize {
return switch (a.*) { return switch (a.*) {
ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}),
ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}),
}; };
} }
}; };

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const expect = std.testing.expect; const expect = std.testing.expect;
var new_stack_bytes: [1024]u8 = undefined; var new_stack_bytes: [1024]u8 align(16) = undefined;
test "calling a function with a new stack" { test "calling a function with a new stack" {
const arg = 1234; const arg = 1234;

View File

@ -33,3 +33,8 @@ test "void optional" {
var x: ?void = {}; var x: ?void = {};
expect(x != null); expect(x != null);
} }
test "void array as a local variable initializer" {
var x = [_]void{{}} ** 1004;
var y = x[0];
}

View File

@ -2,6 +2,8 @@ const std = @import("std");
const debug = std.debug; const debug = std.debug;
const warn = debug.warn; const warn = debug.warn;
const build = std.build; const build = std.build;
pub const Target = build.Target;
pub const CrossTarget = build.CrossTarget;
const Buffer = std.Buffer; const Buffer = std.Buffer;
const io = std.io; const io = std.io;
const fs = std.fs; const fs = std.fs;
@ -20,24 +22,18 @@ const runtime_safety = @import("runtime_safety.zig");
const translate_c = @import("translate_c.zig"); const translate_c = @import("translate_c.zig");
const gen_h = @import("gen_h.zig"); const gen_h = @import("gen_h.zig");
const TestTarget = struct { const test_targets = [_]CrossTarget{
os: builtin.Os, CrossTarget{
arch: builtin.Arch,
abi: builtin.Abi,
};
const test_targets = [_]TestTarget{
TestTarget{
.os = .linux, .os = .linux,
.arch = .x86_64, .arch = .x86_64,
.abi = .gnu, .abi = .gnu,
}, },
TestTarget{ CrossTarget{
.os = .macosx, .os = .macosx,
.arch = .x86_64, .arch = .x86_64,
.abi = .gnu, .abi = .gnu,
}, },
TestTarget{ CrossTarget{
.os = .windows, .os = .windows,
.arch = .x86_64, .arch = .x86_64,
.abi = .msvc, .abi = .msvc,
@ -568,6 +564,7 @@ pub const CompileErrorContext = struct {
link_libc: bool, link_libc: bool,
is_exe: bool, is_exe: bool,
is_test: bool, is_test: bool,
target: Target = .Native,
const SourceFile = struct { const SourceFile = struct {
filename: []const u8, filename: []const u8,
@ -655,6 +652,14 @@ pub const CompileErrorContext = struct {
zig_args.append("--output-dir") catch unreachable; zig_args.append("--output-dir") catch unreachable;
zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable; zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable;
switch (self.case.target) {
.Native => {},
.Cross => {
try zig_args.append("-target");
try zig_args.append(try self.case.target.zigTriple(b.allocator));
},
}
switch (self.build_mode) { switch (self.build_mode) {
Mode.Debug => {}, Mode.Debug => {},
Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable, Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable,