Resolved merge conflict.

This commit is contained in:
Alexandros Naskos 2018-05-01 13:09:34 +03:00
commit 255c0ef406
58 changed files with 5054 additions and 1601 deletions

View File

@ -415,6 +415,9 @@ set(ZIG_CPP_SOURCES
set(ZIG_STD_FILES
"array_list.zig"
"atomic/index.zig"
"atomic/stack.zig"
"atomic/queue.zig"
"base64.zig"
"buf_map.zig"
"buf_set.zig"
@ -498,6 +501,28 @@ set(ZIG_STD_FILES
"math/tan.zig"
"math/tanh.zig"
"math/trunc.zig"
"math/complex/abs.zig"
"math/complex/acosh.zig"
"math/complex/acos.zig"
"math/complex/arg.zig"
"math/complex/asinh.zig"
"math/complex/asin.zig"
"math/complex/atanh.zig"
"math/complex/atan.zig"
"math/complex/conj.zig"
"math/complex/cosh.zig"
"math/complex/cos.zig"
"math/complex/exp.zig"
"math/complex/index.zig"
"math/complex/ldexp.zig"
"math/complex/log.zig"
"math/complex/pow.zig"
"math/complex/proj.zig"
"math/complex/sinh.zig"
"math/complex/sin.zig"
"math/complex/sqrt.zig"
"math/complex/tanh.zig"
"math/complex/tan.zig"
"mem.zig"
"net.zig"
"os/child_process.zig"

View File

@ -1258,7 +1258,7 @@ void init_fn_type_id(FnTypeId *fn_type_id, AstNode *proto_node, size_t param_cou
}
fn_type_id->param_count = fn_proto->params.length;
fn_type_id->param_info = allocate_nonzero<FnTypeParamInfo>(param_count_alloc);
fn_type_id->param_info = allocate<FnTypeParamInfo>(param_count_alloc);
fn_type_id->next_param_index = 0;
fn_type_id->is_var_args = fn_proto->is_var_args;
}
@ -6131,4 +6131,3 @@ bool type_can_fail(TypeTableEntry *type_entry) {
bool fn_type_can_fail(FnTypeId *fn_type_id) {
return type_can_fail(fn_type_id->return_type) || fn_type_id->cc == CallingConventionAsync;
}

View File

@ -3161,6 +3161,9 @@ static IrInstruction *ir_gen_block(IrBuilder *irb, Scope *parent_scope, AstNode
if (block_node->data.block.name == nullptr || incoming_blocks.length == 0) {
return noreturn_return_value;
}
ir_set_cursor_at_end_and_append_block(irb, scope_block->end_block);
return ir_build_phi(irb, parent_scope, block_node, incoming_blocks.length, incoming_blocks.items, incoming_values.items);
} else {
incoming_blocks.append(irb->current_basic_block);
incoming_values.append(ir_mark_gen(ir_build_const_void(irb, parent_scope, block_node)));
@ -6187,16 +6190,10 @@ static IrInstruction *ir_gen_err_set_decl(IrBuilder *irb, Scope *parent_scope, A
buf_init_from_buf(&err_set_type->name, type_name);
err_set_type->is_copyable = true;
err_set_type->data.error_set.err_count = err_count;
if (err_count == 0) {
err_set_type->zero_bits = true;
err_set_type->di_type = irb->codegen->builtin_types.entry_void->di_type;
} else {
err_set_type->type_ref = irb->codegen->builtin_types.entry_global_error_set->type_ref;
err_set_type->di_type = irb->codegen->builtin_types.entry_global_error_set->di_type;
irb->codegen->error_di_types.append(&err_set_type->di_type);
err_set_type->data.error_set.errors = allocate<ErrorTableEntry *>(err_count);
}
ErrorTableEntry **errors = allocate<ErrorTableEntry *>(irb->codegen->errors_by_index.length + err_count);
@ -8138,7 +8135,7 @@ static void update_errors_helper(CodeGen *g, ErrorTableEntry ***errors, size_t *
*errors = reallocate(*errors, old_errors_count, *errors_count);
}
static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, IrInstruction **instructions, size_t instruction_count) {
static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_node, TypeTableEntry *expected_type, IrInstruction **instructions, size_t instruction_count) {
assert(instruction_count >= 1);
IrInstruction *prev_inst = instructions[0];
if (type_is_invalid(prev_inst->value.type)) {
@ -8185,16 +8182,6 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
continue;
}
if (prev_type->id == TypeTableEntryIdNullLit) {
prev_inst = cur_inst;
continue;
}
if (cur_type->id == TypeTableEntryIdNullLit) {
any_are_null = true;
continue;
}
if (prev_type->id == TypeTableEntryIdErrorSet) {
assert(err_set_type != nullptr);
if (cur_type->id == TypeTableEntryIdErrorSet) {
@ -8454,6 +8441,16 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
}
}
if (prev_type->id == TypeTableEntryIdNullLit) {
prev_inst = cur_inst;
continue;
}
if (cur_type->id == TypeTableEntryIdNullLit) {
any_are_null = true;
continue;
}
if (types_match_const_cast_only(ira, prev_type, cur_type, source_node).id == ConstCastResultIdOk) {
continue;
}
@ -8637,6 +8634,10 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
} else if (err_set_type != nullptr) {
if (prev_inst->value.type->id == TypeTableEntryIdErrorSet) {
return err_set_type;
} else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) {
return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type->data.error_union.payload_type);
} else if (expected_type != nullptr && expected_type->id == TypeTableEntryIdErrorUnion) {
return get_error_union_type(ira->codegen, err_set_type, expected_type->data.error_union.payload_type);
} else {
if (prev_inst->value.type->id == TypeTableEntryIdNumLitInt ||
prev_inst->value.type->id == TypeTableEntryIdNumLitFloat)
@ -8648,8 +8649,6 @@ static TypeTableEntry *ir_resolve_peer_types(IrAnalyze *ira, AstNode *source_nod
ir_add_error_node(ira, source_node,
buf_sprintf("unable to make error union out of null literal"));
return ira->codegen->builtin_types.entry_invalid;
} else if (prev_inst->value.type->id == TypeTableEntryIdErrorUnion) {
return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type->data.error_union.payload_type);
} else {
return get_error_union_type(ira->codegen, err_set_type, prev_inst->value.type);
}
@ -10672,7 +10671,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp
}
IrInstruction *instructions[] = {op1, op2};
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, source_node, instructions, 2);
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, source_node, nullptr, instructions, 2);
if (type_is_invalid(resolved_type))
return resolved_type;
type_ensure_zero_bits_known(ira->codegen, resolved_type);
@ -11062,7 +11061,7 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
IrInstruction *op1 = bin_op_instruction->op1->other;
IrInstruction *op2 = bin_op_instruction->op2->other;
IrInstruction *instructions[] = {op1, op2};
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, instructions, 2);
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, bin_op_instruction->base.source_node, nullptr, instructions, 2);
if (type_is_invalid(resolved_type))
return resolved_type;
IrBinOp op_id = bin_op_instruction->op_id;
@ -13031,7 +13030,7 @@ static TypeTableEntry *ir_analyze_instruction_phi(IrAnalyze *ira, IrInstructionP
return first_value->value.type;
}
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, phi_instruction->base.source_node,
TypeTableEntry *resolved_type = ir_resolve_peer_types(ira, phi_instruction->base.source_node, nullptr,
new_incoming_values.items, new_incoming_values.length);
if (type_is_invalid(resolved_type))
return resolved_type;
@ -13928,6 +13927,15 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
}
} else if (child_type->id == TypeTableEntryIdFn) {
if (buf_eql_str(field_name, "ReturnType")) {
if (child_type->data.fn.fn_type_id.return_type == nullptr) {
// Return type can only ever be null, if the function is generic
assert(child_type->data.fn.is_generic);
ir_add_error(ira, &field_ptr_instruction->base,
buf_sprintf("ReturnType has not been resolved because '%s' is generic", buf_ptr(&child_type->name)));
return ira->codegen->builtin_types.entry_invalid;
}
bool ptr_is_const = true;
bool ptr_is_volatile = false;
return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
@ -18816,6 +18824,16 @@ static TypeTableEntry *ir_analyze_instruction_arg_type(IrAnalyze *ira, IrInstruc
ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
out_val->data.x_type = fn_type_id->param_info[arg_index].type;
if (out_val->data.x_type == nullptr) {
// Args are only unresolved if our function is generic.
assert(fn_type->data.fn.is_generic);
ir_add_error(ira, arg_index_inst,
buf_sprintf("@ArgType could not resolve the type of arg %" ZIG_PRI_u64 " because '%s' is generic",
arg_index, buf_ptr(&fn_type->name)));
return ira->codegen->builtin_types.entry_invalid;
}
return ira->codegen->builtin_types.entry_type;
}
@ -19121,6 +19139,11 @@ static TypeTableEntry *ir_analyze_instruction_atomic_rmw(IrAnalyze *ira, IrInstr
} else {
if (!ir_resolve_atomic_order(ira, instruction->ordering->other, &ordering))
return ira->codegen->builtin_types.entry_invalid;
if (ordering == AtomicOrderUnordered) {
ir_add_error(ira, instruction->ordering,
buf_sprintf("@atomicRmw atomic ordering must not be Unordered"));
return ira->codegen->builtin_types.entry_invalid;
}
}
if (instr_is_comptime(casted_operand) && instr_is_comptime(casted_ptr) && casted_ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
@ -19656,7 +19679,7 @@ TypeTableEntry *ir_analyze(CodeGen *codegen, IrExecutable *old_exec, IrExecutabl
} else if (ira->src_implicit_return_type_list.length == 0) {
return codegen->builtin_types.entry_unreachable;
} else {
return ir_resolve_peer_types(ira, expected_type_source_node, ira->src_implicit_return_type_list.items,
return ir_resolve_peer_types(ira, expected_type_source_node, expected_type, ira->src_implicit_return_type_list.items,
ira->src_implicit_return_type_list.length);
}
}

7
std/atomic/index.zig Normal file
View File

@ -0,0 +1,7 @@
pub const Stack = @import("stack.zig").Stack;
pub const Queue = @import("queue.zig").Queue;
test "std.atomic" {
_ = @import("stack.zig").Stack;
_ = @import("queue.zig").Queue;
}

120
std/atomic/queue.zig Normal file
View File

@ -0,0 +1,120 @@
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
const AtomicRmwOp = builtin.AtomicRmwOp;
/// Many reader, many writer, non-allocating, thread-safe, lock-free
pub fn Queue(comptime T: type) type {
return struct {
head: &Node,
tail: &Node,
root: Node,
pub const Self = this;
pub const Node = struct {
next: ?&Node,
data: T,
};
// TODO: well defined copy elision: https://github.com/zig-lang/zig/issues/287
pub fn init(self: &Self) void {
self.root.next = null;
self.head = &self.root;
self.tail = &self.root;
}
pub fn put(self: &Self, node: &Node) void {
node.next = null;
const tail = @atomicRmw(&Node, &self.tail, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst);
_ = @atomicRmw(?&Node, &tail.next, AtomicRmwOp.Xchg, node, AtomicOrder.SeqCst);
}
pub fn get(self: &Self) ?&Node {
var head = @atomicLoad(&Node, &self.head, AtomicOrder.Acquire);
while (true) {
const node = head.next ?? return null;
head = @cmpxchgWeak(&Node, &self.head, head, node, AtomicOrder.Release, AtomicOrder.Acquire) ?? return node;
}
}
};
}
const std = @import("std");
const Context = struct {
allocator: &std.mem.Allocator,
queue: &Queue(i32),
put_sum: isize,
get_sum: isize,
get_count: usize,
puts_done: u8, // TODO make this a bool
};
const puts_per_thread = 10000;
const put_thread_count = 3;
test "std.atomic.queue" {
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024);
defer direct_allocator.allocator.free(plenty_of_memory);
var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
var a = &fixed_buffer_allocator.allocator;
var queue: Queue(i32) = undefined;
queue.init();
var context = Context {
.allocator = a,
.queue = &queue,
.put_sum = 0,
.get_sum = 0,
.puts_done = 0,
.get_count = 0,
};
var putters: [put_thread_count]&std.os.Thread = undefined;
for (putters) |*t| {
*t = try std.os.spawnThread(&context, startPuts);
}
var getters: [put_thread_count]&std.os.Thread = undefined;
for (getters) |*t| {
*t = try std.os.spawnThread(&context, startGets);
}
for (putters) |t| t.wait();
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
for (getters) |t| t.wait();
std.debug.assert(context.put_sum == context.get_sum);
std.debug.assert(context.get_count == puts_per_thread * put_thread_count);
}
fn startPuts(ctx: &Context) u8 {
var put_count: usize = puts_per_thread;
var r = std.rand.DefaultPrng.init(0xdeadbeef);
while (put_count != 0) : (put_count -= 1) {
std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
const x = @bitCast(i32, r.random.scalar(u32));
const node = ctx.allocator.create(Queue(i32).Node) catch unreachable;
node.data = x;
ctx.queue.put(node);
_ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst);
}
return 0;
}
fn startGets(ctx: &Context) u8 {
while (true) {
while (ctx.queue.get()) |node| {
std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
_ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst);
_ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst);
}
if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) {
break;
}
}
return 0;
}

126
std/atomic/stack.zig Normal file
View File

@ -0,0 +1,126 @@
const builtin = @import("builtin");
const AtomicOrder = builtin.AtomicOrder;
/// Many reader, many writer, non-allocating, thread-safe, lock-free
pub fn Stack(comptime T: type) type {
return struct {
root: ?&Node,
pub const Self = this;
pub const Node = struct {
next: ?&Node,
data: T,
};
pub fn init() Self {
return Self {
.root = null,
};
}
/// push operation, but only if you are the first item in the stack. if you did not succeed in
/// being the first item in the stack, returns the other item that was there.
pub fn pushFirst(self: &Self, node: &Node) ?&Node {
node.next = null;
return @cmpxchgStrong(?&Node, &self.root, null, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst);
}
pub fn push(self: &Self, node: &Node) void {
var root = @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst);
while (true) {
node.next = root;
root = @cmpxchgWeak(?&Node, &self.root, root, node, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? break;
}
}
pub fn pop(self: &Self) ?&Node {
var root = @atomicLoad(?&Node, &self.root, AtomicOrder.Acquire);
while (true) {
root = @cmpxchgWeak(?&Node, &self.root, root, (root ?? return null).next, AtomicOrder.SeqCst, AtomicOrder.SeqCst) ?? return root;
}
}
pub fn isEmpty(self: &Self) bool {
return @atomicLoad(?&Node, &self.root, AtomicOrder.SeqCst) == null;
}
};
}
const std = @import("std");
const Context = struct {
allocator: &std.mem.Allocator,
stack: &Stack(i32),
put_sum: isize,
get_sum: isize,
get_count: usize,
puts_done: u8, // TODO make this a bool
};
const puts_per_thread = 1000;
const put_thread_count = 3;
test "std.atomic.stack" {
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
var plenty_of_memory = try direct_allocator.allocator.alloc(u8, 64 * 1024 * 1024);
defer direct_allocator.allocator.free(plenty_of_memory);
var fixed_buffer_allocator = std.heap.ThreadSafeFixedBufferAllocator.init(plenty_of_memory);
var a = &fixed_buffer_allocator.allocator;
var stack = Stack(i32).init();
var context = Context {
.allocator = a,
.stack = &stack,
.put_sum = 0,
.get_sum = 0,
.puts_done = 0,
.get_count = 0,
};
var putters: [put_thread_count]&std.os.Thread = undefined;
for (putters) |*t| {
*t = try std.os.spawnThread(&context, startPuts);
}
var getters: [put_thread_count]&std.os.Thread = undefined;
for (getters) |*t| {
*t = try std.os.spawnThread(&context, startGets);
}
for (putters) |t| t.wait();
_ = @atomicRmw(u8, &context.puts_done, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
for (getters) |t| t.wait();
std.debug.assert(context.put_sum == context.get_sum);
std.debug.assert(context.get_count == puts_per_thread * put_thread_count);
}
fn startPuts(ctx: &Context) u8 {
var put_count: usize = puts_per_thread;
var r = std.rand.DefaultPrng.init(0xdeadbeef);
while (put_count != 0) : (put_count -= 1) {
std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
const x = @bitCast(i32, r.random.scalar(u32));
const node = ctx.allocator.create(Stack(i32).Node) catch unreachable;
node.data = x;
ctx.stack.push(node);
_ = @atomicRmw(isize, &ctx.put_sum, builtin.AtomicRmwOp.Add, x, AtomicOrder.SeqCst);
}
return 0;
}
fn startGets(ctx: &Context) u8 {
while (true) {
while (ctx.stack.pop()) |node| {
std.os.time.sleep(0, 1); // let the os scheduler be our fuzz
_ = @atomicRmw(isize, &ctx.get_sum, builtin.AtomicRmwOp.Add, node.data, builtin.AtomicOrder.SeqCst);
_ = @atomicRmw(usize, &ctx.get_count, builtin.AtomicRmwOp.Add, 1, builtin.AtomicOrder.SeqCst);
}
if (@atomicLoad(u8, &ctx.puts_done, builtin.AtomicOrder.SeqCst) == 1) {
break;
}
}
return 0;
}

View File

@ -81,3 +81,8 @@ pub const sockaddr = extern struct {
};
pub const sa_family_t = u8;
pub const pthread_attr_t = extern struct {
__sig: c_long,
__opaque: [56]u8,
};

View File

@ -53,3 +53,13 @@ pub extern "c" fn malloc(usize) ?&c_void;
pub extern "c" fn realloc(&c_void, usize) ?&c_void;
pub extern "c" fn free(&c_void) void;
pub extern "c" fn posix_memalign(memptr: &&c_void, alignment: usize, size: usize) c_int;
pub extern "pthread" fn pthread_create(noalias newthread: &pthread_t,
noalias attr: ?&const pthread_attr_t, start_routine: extern fn(?&c_void) ?&c_void,
noalias arg: ?&c_void) c_int;
pub extern "pthread" fn pthread_attr_init(attr: &pthread_attr_t) c_int;
pub extern "pthread" fn pthread_attr_setstack(attr: &pthread_attr_t, stackaddr: &c_void, stacksize: usize) c_int;
pub extern "pthread" fn pthread_attr_destroy(attr: &pthread_attr_t) c_int;
pub extern "pthread" fn pthread_join(thread: pthread_t, arg_return: ?&?&c_void) c_int;
pub const pthread_t = &@OpaqueType();

View File

@ -3,3 +3,8 @@ pub use @import("../os/linux/errno.zig");
pub extern "c" fn getrandom(buf_ptr: &u8, buf_len: usize, flags: c_uint) c_int;
extern "c" fn __errno_location() &c_int;
pub const _errno = __errno_location;
pub const pthread_attr_t = extern struct {
__size: [56]u8,
__align: c_long,
};

View File

@ -1,17 +1,13 @@
// Modify the HashFunction variable to the one wanted to test.
//
// NOTE: The throughput measurement may be slightly lower than other measurements since we run
// through our block alignment functions as well. Be aware when comparing against other tests.
//
// ```
// zig build-exe --release-fast --library c throughput_test.zig
// zig build-exe --release-fast throughput_test.zig
// ./throughput_test
// ```
const std = @import("std");
const c = @cImport({
@cInclude("time.h");
});
const time = std.os.time;
const Timer = time.Timer;
const HashFunction = @import("md5.zig").Md5;
const MiB = 1024 * 1024;
@ -28,13 +24,14 @@ pub fn main() !void {
var h = HashFunction.init();
var offset: usize = 0;
const start = c.clock();
var timer = try Timer.start();
const start = timer.lap();
while (offset < BytesToHash) : (offset += block.len) {
h.update(block[0..]);
}
const end = c.clock();
const end = timer.read();
const elapsed_s = f64(end - start) / f64(c.CLOCKS_PER_SEC);
const elapsed_s = f64(end - start) / time.ns_per_s;
const throughput = u64(BytesToHash / elapsed_s);
try stdout.print("{}: {} MiB/s\n", @typeName(HashFunction), throughput / (1 * MiB));

View File

@ -12,13 +12,79 @@ pub const FloatDecimal = struct {
exp: i32,
};
pub const RoundMode = enum {
// Round only the fractional portion (e.g. 1234.23 has precision 2)
Decimal,
// Round the entire whole/fractional portion (e.g. 1.23423e3 has precision 5)
Scientific,
};
/// Round a FloatDecimal as returned by errol3 to the specified fractional precision.
/// All digits after the specified precision should be considered invalid.
pub fn roundToPrecision(float_decimal: &FloatDecimal, precision: usize, mode: RoundMode) void {
// The round digit refers to the index which we should look at to determine
// whether we need to round to match the specified precision.
var round_digit: usize = 0;
switch (mode) {
RoundMode.Decimal => {
if (float_decimal.exp >= 0) {
round_digit = precision + usize(float_decimal.exp);
} else {
// if a small negative exp, then adjust we need to offset by the number
// of leading zeros that will occur.
const min_exp_required = usize(-float_decimal.exp);
if (precision > min_exp_required) {
round_digit = precision - min_exp_required;
}
}
},
RoundMode.Scientific => {
round_digit = 1 + precision;
},
}
// It suffices to look at just this digit. We don't round and propagate say 0.04999 to 0.05
// first, and then to 0.1 in the case of a {.1} single precision.
// Find the digit which will signify the round point and start rounding backwards.
if (round_digit < float_decimal.digits.len and float_decimal.digits[round_digit] - '0' >= 5) {
assert(round_digit >= 0);
var i = round_digit;
while (true) {
if (i == 0) {
// Rounded all the way past the start. This was of the form 9.999...
// Slot the new digit in place and increase the exponent.
float_decimal.exp += 1;
// Re-size the buffer to use the reserved leading byte.
const one_before = @intToPtr(&u8, @ptrToInt(&float_decimal.digits[0]) - 1);
float_decimal.digits = one_before[0..float_decimal.digits.len + 1];
float_decimal.digits[0] = '1';
return;
}
i -= 1;
const new_value = (float_decimal.digits[i] - '0' + 1) % 10;
float_decimal.digits[i] = new_value + '0';
// must continue rounding until non-9
if (new_value != 0) {
return;
}
}
}
}
/// Corrected Errol3 double to ASCII conversion.
pub fn errol3(value: f64, buffer: []u8) FloatDecimal {
const bits = @bitCast(u64, value);
const i = tableLowerBound(bits);
if (i < enum3.len and enum3[i] == bits) {
const data = enum3_data[i];
const digits = buffer[0..data.str.len];
const digits = buffer[1..data.str.len + 1];
mem.copy(u8, digits, data.str);
return FloatDecimal {
.digits = digits,
@ -98,7 +164,11 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
}
// digit generation
var buf_index: usize = 0;
// We generate digits starting at index 1. If rounding a buffer later then it may be
// required to generate a preceeding digit in some cases (9.999) in which case we use
// the 0-index for this extra digit.
var buf_index: usize = 1;
while (true) {
var hdig = u8(math.floor(high.val));
if ((high.val == f64(hdig)) and (high.off < 0))
@ -128,7 +198,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal {
buf_index += 1;
return FloatDecimal {
.digits = buffer[0..buf_index],
.digits = buffer[1..buf_index],
.exp = exp,
};
}
@ -189,6 +259,9 @@ fn gethi(in: f64) f64 {
/// Normalize the number by factoring in the error.
/// @hp: The float pair.
fn hpNormalize(hp: &HP) void {
// Required to avoid segfaults causing buffer overrun during errol3 digit output termination.
@setFloatMode(this, @import("builtin").FloatMode.Strict);
const val = hp.val;
hp.val += hp.off;

View File

@ -4,7 +4,7 @@ const debug = std.debug;
const assert = debug.assert;
const mem = std.mem;
const builtin = @import("builtin");
const errol3 = @import("errol/index.zig").errol3;
const errol = @import("errol/index.zig");
const max_int_digits = 65;
@ -22,6 +22,8 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
IntegerWidth,
Float,
FloatWidth,
FloatScientific,
FloatScientificWidth,
Character,
Buf,
BufWidth,
@ -87,6 +89,9 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
's' => {
state = State.Buf;
},
'e' => {
state = State.FloatScientific;
},
'.' => {
state = State.Float;
},
@ -133,9 +138,33 @@ pub fn format(context: var, comptime Errors: type, output: fn(@typeOf(context),
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.FloatScientific => switch (c) {
'}' => {
try formatFloatScientific(args[next_arg], null, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {
width_start = i;
state = State.FloatScientificWidth;
},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.FloatScientificWidth => switch (c) {
'}' => {
width = comptime (parseUnsigned(usize, fmt[width_start..i], 10) catch unreachable);
try formatFloatScientific(args[next_arg], width, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
'0' ... '9' => {},
else => @compileError("Unexpected character in format string: " ++ []u8{c}),
},
State.Float => switch (c) {
'}' => {
try formatFloatDecimal(args[next_arg], 0, context, Errors, output);
try formatFloatDecimal(args[next_arg], null, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
@ -199,7 +228,7 @@ pub fn formatValue(value: var, context: var, comptime Errors: type, output: fn(@
return formatInt(value, 10, false, 0, context, Errors, output);
},
builtin.TypeId.Float => {
return formatFloat(value, context, Errors, output);
return formatFloatScientific(value, null, context, Errors, output);
},
builtin.TypeId.Void => {
return output(context, "void");
@ -257,26 +286,67 @@ pub fn formatBuf(buf: []const u8, width: usize,
}
}
pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
// Print a float in scientific notation to the specified precision. Null uses full precision.
// It should be the case that every full precision, printed value can be re-parsed back to the
// same type unambiguously.
pub fn formatFloatScientific(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
var x = f64(value);
// Errol doesn't handle these special cases.
if (math.isNan(x)) {
return output(context, "NaN");
}
if (math.signbit(x)) {
try output(context, "-");
x = -x;
}
if (math.isNan(x)) {
return output(context, "nan");
}
if (math.isPositiveInf(x)) {
return output(context, "Infinity");
return output(context, "inf");
}
if (x == 0.0) {
return output(context, "0.0");
try output(context, "0");
if (maybe_precision) |precision| {
if (precision != 0) {
try output(context, ".");
var i: usize = 0;
while (i < precision) : (i += 1) {
try output(context, "0");
}
}
} else {
try output(context, ".0");
}
try output(context, "e+00");
return;
}
var buffer: [32]u8 = undefined;
const float_decimal = errol3(x, buffer[0..]);
var float_decimal = errol.errol3(x, buffer[0..]);
if (maybe_precision) |precision| {
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific);
try output(context, float_decimal.digits[0..1]);
// {e0} case prints no `.`
if (precision != 0) {
try output(context, ".");
var printed: usize = 0;
if (float_decimal.digits.len > 1) {
const num_digits = math.min(float_decimal.digits.len, precision + 1);
try output(context, float_decimal.digits[1 .. num_digits]);
printed += num_digits - 1;
}
while (printed < precision) : (printed += 1) {
try output(context, "0");
}
}
} else {
try output(context, float_decimal.digits[0..1]);
try output(context, ".");
if (float_decimal.digits.len > 1) {
@ -284,55 +354,170 @@ pub fn formatFloat(value: var, context: var, comptime Errors: type, output: fn(@
math.min(usize(9), float_decimal.digits.len)
else
float_decimal.digits.len;
try output(context, float_decimal.digits[1 .. num_digits]);
} else {
try output(context, "0");
}
}
if (float_decimal.exp != 1) {
try output(context, "e");
try formatInt(float_decimal.exp - 1, 10, false, 0, context, Errors, output);
const exp = float_decimal.exp - 1;
if (exp >= 0) {
try output(context, "+");
if (exp > -10 and exp < 10) {
try output(context, "0");
}
try formatInt(exp, 10, false, 0, context, Errors, output);
} else {
try output(context, "-");
if (exp > -10 and exp < 10) {
try output(context, "0");
}
try formatInt(-exp, 10, false, 0, context, Errors, output);
}
}
pub fn formatFloatDecimal(value: var, precision: usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
// Print a float of the format x.yyyyy where the number of y is specified by the precision argument.
// By default floats are printed at full precision (no rounding).
pub fn formatFloatDecimal(value: var, maybe_precision: ?usize, context: var, comptime Errors: type, output: fn(@typeOf(context), []const u8)Errors!void) Errors!void {
var x = f64(value);
// Errol doesn't handle these special cases.
if (math.isNan(x)) {
return output(context, "NaN");
}
if (math.signbit(x)) {
try output(context, "-");
x = -x;
}
if (math.isNan(x)) {
return output(context, "nan");
}
if (math.isPositiveInf(x)) {
return output(context, "Infinity");
return output(context, "inf");
}
if (x == 0.0) {
return output(context, "0.0");
}
try output(context, "0");
var buffer: [32]u8 = undefined;
const float_decimal = errol3(x, buffer[0..]);
const num_left_digits = if (float_decimal.exp > 0) usize(float_decimal.exp) else 1;
try output(context, float_decimal.digits[0 .. num_left_digits]);
if (maybe_precision) |precision| {
if (precision != 0) {
try output(context, ".");
if (float_decimal.digits.len > 1) {
const num_valid_digtis = if (@typeOf(value) == f32) math.min(usize(7), float_decimal.digits.len)
else
float_decimal.digits.len;
const num_right_digits = if (precision != 0)
math.min(precision, (num_valid_digtis-num_left_digits))
else
num_valid_digtis - num_left_digits;
try output(context, float_decimal.digits[num_left_digits .. (num_left_digits + num_right_digits)]);
var i: usize = 0;
while (i < precision) : (i += 1) {
try output(context, "0");
}
} else {
try output(context, ".0");
}
} else {
try output(context, "0");
}
return;
}
// non-special case, use errol3
var buffer: [32]u8 = undefined;
var float_decimal = errol.errol3(x, buffer[0..]);
if (maybe_precision) |precision| {
errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal);
// exp < 0 means the leading is always 0 as errol result is normalized.
var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
if (num_digits_whole > 0) {
// We may have to zero pad, for instance 1e4 requires zero padding.
try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
var i = num_digits_whole_no_pad;
while (i < num_digits_whole) : (i += 1) {
try output(context, "0");
}
} else {
try output(context , "0");
}
// {.0} special case doesn't want a trailing '.'
if (precision == 0) {
return;
}
try output(context, ".");
// Keep track of fractional count printed for case where we pre-pad then post-pad with 0's.
var printed: usize = 0;
// Zero-fill until we reach significant digits or run out of precision.
if (float_decimal.exp <= 0) {
const zero_digit_count = usize(-float_decimal.exp);
const zeros_to_print = math.min(zero_digit_count, precision);
var i: usize = 0;
while (i < zeros_to_print) : (i += 1) {
try output(context, "0");
printed += 1;
}
if (printed >= precision) {
return;
}
}
// Remaining fractional portion, zero-padding if insufficient.
debug.assert(precision >= printed);
if (num_digits_whole_no_pad + precision - printed < float_decimal.digits.len) {
try output(context, float_decimal.digits[num_digits_whole_no_pad .. num_digits_whole_no_pad + precision - printed]);
return;
} else {
try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
printed += float_decimal.digits.len - num_digits_whole_no_pad;
while (printed < precision) : (printed += 1) {
try output(context, "0");
}
}
} else {
// exp < 0 means the leading is always 0 as errol result is normalized.
var num_digits_whole = if (float_decimal.exp > 0) usize(float_decimal.exp) else 0;
// the actual slice into the buffer, we may need to zero-pad between num_digits_whole and this.
var num_digits_whole_no_pad = math.min(num_digits_whole, float_decimal.digits.len);
if (num_digits_whole > 0) {
// We may have to zero pad, for instance 1e4 requires zero padding.
try output(context, float_decimal.digits[0 .. num_digits_whole_no_pad]);
var i = num_digits_whole_no_pad;
while (i < num_digits_whole) : (i += 1) {
try output(context, "0");
}
} else {
try output(context , "0");
}
// Omit `.` if no fractional portion
if (float_decimal.exp >= 0 and num_digits_whole_no_pad == float_decimal.digits.len) {
return;
}
try output(context, ".");
// Zero-fill until we reach significant digits or run out of precision.
if (float_decimal.exp < 0) {
const zero_digit_count = usize(-float_decimal.exp);
var i: usize = 0;
while (i < zero_digit_count) : (i += 1) {
try output(context, "0");
}
}
try output(context, float_decimal.digits[num_digits_whole_no_pad ..]);
}
}
@ -594,36 +779,85 @@ test "fmt.format" {
const result = try bufPrint(buf1[0..], "pointer: {}\n", &value);
assert(mem.startsWith(u8, result, "pointer: Struct@"));
}
// TODO get these tests passing in release modes
// https://github.com/zig-lang/zig/issues/564
if (builtin.mode == builtin.Mode.Debug) {
{
var buf1: [32]u8 = undefined;
const value: f32 = 1.34;
const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
assert(mem.eql(u8, result, "f32: 1.34000003e+00\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f32 = 12.34;
const result = try bufPrint(buf1[0..], "f32: {}\n", value);
assert(mem.eql(u8, result, "f32: 1.23400001e1\n"));
const result = try bufPrint(buf1[0..], "f32: {e}\n", value);
assert(mem.eql(u8, result, "f32: 1.23400001e+01\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = -12.34e10;
const result = try bufPrint(buf1[0..], "f64: {}\n", value);
assert(mem.eql(u8, result, "f64: -1.234e11\n"));
const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
assert(mem.eql(u8, result, "f64: -1.234e+11\n"));
}
{
// This fails on release due to a minor rounding difference.
// --release-fast outputs 9.999960000000001e-40 vs. the expected.
if (builtin.mode == builtin.Mode.Debug) {
var buf1: [32]u8 = undefined;
const value: f64 = 9.999960e-40;
const result = try bufPrint(buf1[0..], "f64: {e}\n", value);
assert(mem.eql(u8, result, "f64: 9.99996e-40\n"));
}
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 1.409706e-42;
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
assert(mem.eql(u8, result, "f64: 1.40971e-42\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = @bitCast(f32, u32(814313563));
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
assert(mem.eql(u8, result, "f64: 1.00000e-09\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = @bitCast(f32, u32(1006632960));
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
assert(mem.eql(u8, result, "f64: 7.81250e-03\n"));
}
{
// libc rounds 1.000005e+05 to 1.00000e+05 but zig does 1.00001e+05.
// In fact, libc doesn't round a lot of 5 cases up when one past the precision point.
var buf1: [32]u8 = undefined;
const value: f64 = @bitCast(f32, u32(1203982400));
const result = try bufPrint(buf1[0..], "f64: {e5}\n", value);
assert(mem.eql(u8, result, "f64: 1.00001e+05\n"));
}
{
var buf1: [32]u8 = undefined;
const result = try bufPrint(buf1[0..], "f64: {}\n", math.nan_f64);
assert(mem.eql(u8, result, "f64: NaN\n"));
assert(mem.eql(u8, result, "f64: nan\n"));
}
{
var buf1: [32]u8 = undefined;
const result = try bufPrint(buf1[0..], "f64: {}\n", -math.nan_f64);
assert(mem.eql(u8, result, "f64: -nan\n"));
}
{
var buf1: [32]u8 = undefined;
const result = try bufPrint(buf1[0..], "f64: {}\n", math.inf_f64);
assert(mem.eql(u8, result, "f64: Infinity\n"));
assert(mem.eql(u8, result, "f64: inf\n"));
}
{
var buf1: [32]u8 = undefined;
const result = try bufPrint(buf1[0..], "f64: {}\n", -math.inf_f64);
assert(mem.eql(u8, result, "f64: -Infinity\n"));
assert(mem.eql(u8, result, "f64: -inf\n"));
}
{
var buf1: [64]u8 = undefined;
const value: f64 = 1.52314e+29;
const result = try bufPrint(buf1[0..], "f64: {.}\n", value);
assert(mem.eql(u8, result, "f64: 152314000000000000000000000000\n"));
}
{
var buf1: [32]u8 = undefined;
@ -635,20 +869,20 @@ test "fmt.format" {
var buf1: [32]u8 = undefined;
const value: f32 = 1234.567;
const result = try bufPrint(buf1[0..], "f32: {.2}\n", value);
assert(mem.eql(u8, result, "f32: 1234.56\n"));
assert(mem.eql(u8, result, "f32: 1234.57\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f32 = -11.1234;
const result = try bufPrint(buf1[0..], "f32: {.4}\n", value);
// -11.1234 is converted to f64 -11.12339... internally (errol3() function takes f64).
// -11.12339... is truncated to -11.1233
assert(mem.eql(u8, result, "f32: -11.1233\n"));
// -11.12339... is rounded back up to -11.1234
assert(mem.eql(u8, result, "f32: -11.1234\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f32 = 91.12345;
const result = try bufPrint(buf1[0..], "f32: {.}\n", value);
const result = try bufPrint(buf1[0..], "f32: {.5}\n", value);
assert(mem.eql(u8, result, "f32: 91.12345\n"));
}
{
@ -657,7 +891,98 @@ test "fmt.format" {
const result = try bufPrint(buf1[0..], "f64: {.10}\n", value);
assert(mem.eql(u8, result, "f64: 91.1234567890\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 0.0;
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.00000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 5.700;
const result = try bufPrint(buf1[0..], "f64: {.0}\n", value);
assert(mem.eql(u8, result, "f64: 6\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 9.999;
const result = try bufPrint(buf1[0..], "f64: {.1}\n", value);
assert(mem.eql(u8, result, "f64: 10.0\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 1.0;
const result = try bufPrint(buf1[0..], "f64: {.3}\n", value);
assert(mem.eql(u8, result, "f64: 1.000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 0.0003;
const result = try bufPrint(buf1[0..], "f64: {.8}\n", value);
assert(mem.eql(u8, result, "f64: 0.00030000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 1.40130e-45;
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.00000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = 9.999960e-40;
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.00000\n"));
}
// libc checks
{
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(916964781)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.00001\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(925353389)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.00001\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(1036831278)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.10000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(1065353133)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 1.00000\n"));
}
{
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(1092616192)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 10.00000\n"));
}
// libc differences
{
var buf1: [32]u8 = undefined;
// This is 0.015625 exactly according to gdb. We thus round down,
// however glibc rounds up for some reason. This occurs for all
// floats of the form x.yyyy25 on a precision point.
const value: f64 = f64(@bitCast(f32, u32(1015021568)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 0.01563\n"));
}
// std-windows-x86_64-Debug-bare test case fails
{
// errol3 rounds to ... 630 but libc rounds to ...632. Grisu3
// also rounds to 630 so I'm inclined to believe libc is not
// optimal here.
var buf1: [32]u8 = undefined;
const value: f64 = f64(@bitCast(f32, u32(1518338049)));
const result = try bufPrint(buf1[0..], "f64: {.5}\n", value);
assert(mem.eql(u8, result, "f64: 18014400656965630.00000\n"));
}
}

View File

@ -47,13 +47,6 @@ pub const DirectAllocator = struct {
const HeapHandle = if (builtin.os == Os.windows) os.windows.HANDLE else void;
//pub const canary_bytes = []u8 {48, 239, 128, 46, 18, 49, 147, 9, 195, 59, 203, 3, 245, 54, 9, 122};
//pub const want_safety = switch (builtin.mode) {
// builtin.Mode.Debug => true,
// builtin.Mode.ReleaseSafe => true,
// else => false,
//};
pub fn init() DirectAllocator {
return DirectAllocator {
.allocator = Allocator {
@ -98,7 +91,7 @@ pub const DirectAllocator = struct {
const unused_start = addr;
const unused_len = aligned_addr - 1 - unused_start;
var err = p.munmap(@intToPtr(&u8, unused_start), unused_len);
var err = p.munmap(unused_start, unused_len);
debug.assert(p.getErrno(err) == 0);
//It is impossible that there is an unoccupied page at the top of our
@ -139,7 +132,7 @@ pub const DirectAllocator = struct {
const rem = @rem(new_addr_end, os.page_size);
const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem);
if (old_addr_end > new_addr_end_rounded) {
_ = os.posix.munmap(@intToPtr(&u8, new_addr_end_rounded), old_addr_end - new_addr_end_rounded);
_ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded);
}
return old_mem[0..new_size];
}
@ -177,7 +170,7 @@ pub const DirectAllocator = struct {
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => {
_ = os.posix.munmap(bytes.ptr, bytes.len);
_ = os.posix.munmap(@ptrToInt(bytes.ptr), bytes.len);
},
Os.windows => {
const record_addr = @ptrToInt(bytes.ptr) + bytes.len;
@ -298,7 +291,7 @@ pub const FixedBufferAllocator = struct {
fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(FixedBufferAllocator, "allocator", allocator);
const addr = @ptrToInt(&self.buffer[self.end_index]);
const addr = @ptrToInt(self.buffer.ptr) + self.end_index;
const rem = @rem(addr, alignment);
const march_forward_bytes = if (rem == 0) 0 else (alignment - rem);
const adjusted_index = self.end_index + march_forward_bytes;
@ -325,6 +318,54 @@ pub const FixedBufferAllocator = struct {
fn free(allocator: &Allocator, bytes: []u8) void { }
};
/// lock free
pub const ThreadSafeFixedBufferAllocator = struct {
allocator: Allocator,
end_index: usize,
buffer: []u8,
pub fn init(buffer: []u8) ThreadSafeFixedBufferAllocator {
return ThreadSafeFixedBufferAllocator {
.allocator = Allocator {
.allocFn = alloc,
.reallocFn = realloc,
.freeFn = free,
},
.buffer = buffer,
.end_index = 0,
};
}
fn alloc(allocator: &Allocator, n: usize, alignment: u29) ![]u8 {
const self = @fieldParentPtr(ThreadSafeFixedBufferAllocator, "allocator", allocator);
var end_index = @atomicLoad(usize, &self.end_index, builtin.AtomicOrder.SeqCst);
while (true) {
const addr = @ptrToInt(self.buffer.ptr) + end_index;
const rem = @rem(addr, alignment);
const march_forward_bytes = if (rem == 0) 0 else (alignment - rem);
const adjusted_index = end_index + march_forward_bytes;
const new_end_index = adjusted_index + n;
if (new_end_index > self.buffer.len) {
return error.OutOfMemory;
}
end_index = @cmpxchgWeak(usize, &self.end_index, end_index, new_end_index,
builtin.AtomicOrder.SeqCst, builtin.AtomicOrder.SeqCst) ?? return self.buffer[adjusted_index .. new_end_index];
}
}
fn realloc(allocator: &Allocator, old_mem: []u8, new_size: usize, alignment: u29) ![]u8 {
if (new_size <= old_mem.len) {
return old_mem[0..new_size];
} else {
const result = try alloc(allocator, new_size, alignment);
mem.copy(u8, result, old_mem);
return result;
}
}
fn free(allocator: &Allocator, bytes: []u8) void { }
};
test "c_allocator" {
@ -363,6 +404,13 @@ test "FixedBufferAllocator" {
try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator);
}
test "ThreadSafeFixedBufferAllocator" {
var fixed_buffer_allocator = ThreadSafeFixedBufferAllocator.init(test_fixed_buffer_allocator_memory[0..]);
try testAllocator(&fixed_buffer_allocator.allocator);
try testAllocatorLargeAlignment(&fixed_buffer_allocator.allocator);
}
fn testAllocator(allocator: &mem.Allocator) !void {
var slice = try allocator.alloc(&i32, 100);

View File

@ -8,6 +8,7 @@ pub const HashMap = @import("hash_map.zig").HashMap;
pub const LinkedList = @import("linked_list.zig").LinkedList;
pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;
pub const atomic = @import("atomic/index.zig");
pub const base64 = @import("base64.zig");
pub const build = @import("build.zig");
pub const c = @import("c/index.zig");
@ -34,6 +35,7 @@ pub const zig = @import("zig/index.zig");
test "std" {
// run tests from these
_ = @import("atomic/index.zig");
_ = @import("array_list.zig");
_ = @import("buf_map.zig");
_ = @import("buf_set.zig");

18
std/math/complex/abs.zig Normal file
View File

@ -0,0 +1,18 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn abs(z: var) @typeOf(z.re) {
const T = @typeOf(z.re);
return math.hypot(T, z.re, z.im);
}
const epsilon = 0.0001;
test "complex.cabs" {
const a = Complex(f32).new(5, 3);
const c = abs(a);
debug.assert(math.approxEq(f32, c, 5.83095, epsilon));
}

21
std/math/complex/acos.zig Normal file
View File

@ -0,0 +1,21 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn acos(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const q = cmath.asin(z);
return Complex(T).new(T(math.pi) / 2 - q.re, -q.im);
}
const epsilon = 0.0001;
test "complex.cacos" {
const a = Complex(f32).new(5, 3);
const c = acos(a);
debug.assert(math.approxEq(f32, c.re, 0.546975, epsilon));
debug.assert(math.approxEq(f32, c.im, -2.452914, epsilon));
}

View File

@ -0,0 +1,21 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn acosh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const q = cmath.acos(z);
return Complex(T).new(-q.im, q.re);
}
const epsilon = 0.0001;
test "complex.cacosh" {
const a = Complex(f32).new(5, 3);
const c = acosh(a);
debug.assert(math.approxEq(f32, c.re, 2.452914, epsilon));
debug.assert(math.approxEq(f32, c.im, 0.546975, epsilon));
}

18
std/math/complex/arg.zig Normal file
View File

@ -0,0 +1,18 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn arg(z: var) @typeOf(z.re) {
const T = @typeOf(z.re);
return math.atan2(T, z.im, z.re);
}
const epsilon = 0.0001;
test "complex.carg" {
const a = Complex(f32).new(5, 3);
const c = arg(a);
debug.assert(math.approxEq(f32, c, 0.540420, epsilon));
}

27
std/math/complex/asin.zig Normal file
View File

@ -0,0 +1,27 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn asin(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const x = z.re;
const y = z.im;
const p = Complex(T).new(1.0 - (x - y) * (x + y), -2.0 * x * y);
const q = Complex(T).new(-y, x);
const r = cmath.log(q.add(cmath.sqrt(p)));
return Complex(T).new(r.im, -r.re);
}
const epsilon = 0.0001;
test "complex.casin" {
const a = Complex(f32).new(5, 3);
const c = asin(a);
debug.assert(math.approxEq(f32, c.re, 1.023822, epsilon));
debug.assert(math.approxEq(f32, c.im, 2.452914, epsilon));
}

View File

@ -0,0 +1,22 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn asinh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const q = Complex(T).new(-z.im, z.re);
const r = cmath.asin(q);
return Complex(T).new(r.im, -r.re);
}
const epsilon = 0.0001;
test "complex.casinh" {
const a = Complex(f32).new(5, 3);
const c = asinh(a);
debug.assert(math.approxEq(f32, c.re, 2.459831, epsilon));
debug.assert(math.approxEq(f32, c.im, 0.533999, epsilon));
}

130
std/math/complex/atan.zig Normal file
View File

@ -0,0 +1,130 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn atan(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => atan32(z),
f64 => atan64(z),
else => @compileError("atan not implemented for " ++ @typeName(z)),
};
}
fn redupif32(x: f32) f32 {
const DP1 = 3.140625;
const DP2 = 9.67502593994140625e-4;
const DP3 = 1.509957990978376432e-7;
var t = x / math.pi;
if (t >= 0.0) {
t += 0.5;
} else {
t -= 0.5;
}
const u = f32(i32(t));
return ((x - u * DP1) - u * DP2) - t * DP3;
}
fn atan32(z: &const Complex(f32)) Complex(f32) {
const maxnum = 1.0e38;
const x = z.re;
const y = z.im;
if ((x == 0.0) and (y > 1.0)) {
// overflow
return Complex(f32).new(maxnum, maxnum);
}
const x2 = x * x;
var a = 1.0 - x2 - (y * y);
if (a == 0.0) {
// overflow
return Complex(f32).new(maxnum, maxnum);
}
var t = 0.5 * math.atan2(f32, 2.0 * x, a);
var w = redupif32(t);
t = y - 1.0;
a = x2 + t * t;
if (a == 0.0) {
// overflow
return Complex(f32).new(maxnum, maxnum);
}
t = y + 1.0;
a = (x2 + (t * t)) / a;
return Complex(f32).new(w, 0.25 * math.ln(a));
}
fn redupif64(x: f64) f64 {
const DP1 = 3.14159265160560607910;
const DP2 = 1.98418714791870343106e-9;
const DP3 = 1.14423774522196636802e-17;
var t = x / math.pi;
if (t >= 0.0) {
t += 0.5;
} else {
t -= 0.5;
}
const u = f64(i64(t));
return ((x - u * DP1) - u * DP2) - t * DP3;
}
fn atan64(z: &const Complex(f64)) Complex(f64) {
const maxnum = 1.0e308;
const x = z.re;
const y = z.im;
if ((x == 0.0) and (y > 1.0)) {
// overflow
return Complex(f64).new(maxnum, maxnum);
}
const x2 = x * x;
var a = 1.0 - x2 - (y * y);
if (a == 0.0) {
// overflow
return Complex(f64).new(maxnum, maxnum);
}
var t = 0.5 * math.atan2(f64, 2.0 * x, a);
var w = redupif64(t);
t = y - 1.0;
a = x2 + t * t;
if (a == 0.0) {
// overflow
return Complex(f64).new(maxnum, maxnum);
}
t = y + 1.0;
a = (x2 + (t * t)) / a;
return Complex(f64).new(w, 0.25 * math.ln(a));
}
const epsilon = 0.0001;
test "complex.catan32" {
const a = Complex(f32).new(5, 3);
const c = atan(a);
debug.assert(math.approxEq(f32, c.re, 1.423679, epsilon));
debug.assert(math.approxEq(f32, c.im, 0.086569, epsilon));
}
test "complex.catan64" {
const a = Complex(f64).new(5, 3);
const c = atan(a);
debug.assert(math.approxEq(f64, c.re, 1.423679, epsilon));
debug.assert(math.approxEq(f64, c.im, 0.086569, epsilon));
}

View File

@ -0,0 +1,22 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn atanh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const q = Complex(T).new(-z.im, z.re);
const r = cmath.atan(q);
return Complex(T).new(r.im, -r.re);
}
const epsilon = 0.0001;
test "complex.catanh" {
const a = Complex(f32).new(5, 3);
const c = atanh(a);
debug.assert(math.approxEq(f32, c.re, 0.146947, epsilon));
debug.assert(math.approxEq(f32, c.im, 1.480870, epsilon));
}

17
std/math/complex/conj.zig Normal file
View File

@ -0,0 +1,17 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn conj(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return Complex(T).new(z.re, -z.im);
}
test "complex.conj" {
const a = Complex(f32).new(5, 3);
const c = a.conjugate();
debug.assert(c.re == 5 and c.im == -3);
}

21
std/math/complex/cos.zig Normal file
View File

@ -0,0 +1,21 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn cos(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const p = Complex(T).new(-z.im, z.re);
return cmath.cosh(p);
}
const epsilon = 0.0001;
test "complex.ccos" {
const a = Complex(f32).new(5, 3);
const c = cos(a);
debug.assert(math.approxEq(f32, c.re, 2.855815, epsilon));
debug.assert(math.approxEq(f32, c.im, 9.606383, epsilon));
}

165
std/math/complex/cosh.zig Normal file
View File

@ -0,0 +1,165 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
const ldexp_cexp = @import("ldexp.zig").ldexp_cexp;
pub fn cosh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => cosh32(z),
f64 => cosh64(z),
else => @compileError("cosh not implemented for " ++ @typeName(z)),
};
}
fn cosh32(z: &const Complex(f32)) Complex(f32) {
const x = z.re;
const y = z.im;
const hx = @bitCast(u32, x);
const ix = hx & 0x7fffffff;
const hy = @bitCast(u32, y);
const iy = hy & 0x7fffffff;
if (ix < 0x7f800000 and iy < 0x7f800000) {
if (iy == 0) {
return Complex(f32).new(math.cosh(x), y);
}
// small x: normal case
if (ix < 0x41100000) {
return Complex(f32).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y));
}
// |x|>= 9, so cosh(x) ~= exp(|x|)
if (ix < 0x42b17218) {
// x < 88.7: exp(|x|) won't overflow
const h = math.exp(math.fabs(x)) * 0.5;
return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y));
}
// x < 192.7: scale to avoid overflow
else if (ix < 0x4340b1e7) {
const v = Complex(f32).new(math.fabs(x), y);
const r = ldexp_cexp(v, -1);
return Complex(f32).new(x, y * math.copysign(f32, 1, x));
}
// x >= 192.7: result always overflows
else {
const h = 0x1p127 * x;
return Complex(f32).new(h * h * math.cos(y), h * math.sin(y));
}
}
if (ix == 0 and iy >= 0x7f800000) {
return Complex(f32).new(y - y, math.copysign(f32, 0, x * (y - y)));
}
if (iy == 0 and ix >= 0x7f800000) {
if (hx & 0x7fffff == 0) {
return Complex(f32).new(x * x, math.copysign(f32, 0, x) * y);
}
return Complex(f32).new(x, math.copysign(f32, 0, (x + x) * y));
}
if (ix < 0x7f800000 and iy >= 0x7f800000) {
return Complex(f32).new(y - y, x * (y - y));
}
if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) {
if (iy >= 0x7f800000) {
return Complex(f32).new(x * x, x * (y - y));
}
return Complex(f32).new((x * x) * math.cos(y), x * math.sin(y));
}
return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y));
}
fn cosh64(z: &const Complex(f64)) Complex(f64) {
const x = z.re;
const y = z.im;
const fx = @bitCast(u64, x);
const hx = u32(fx >> 32);
const lx = @truncate(u32, fx);
const ix = hx & 0x7fffffff;
const fy = @bitCast(u64, y);
const hy = u32(fy >> 32);
const ly = @truncate(u32, fy);
const iy = hy & 0x7fffffff;
// nearly non-exceptional case where x, y are finite
if (ix < 0x7ff00000 and iy < 0x7ff00000) {
if (iy | ly == 0) {
return Complex(f64).new(math.cosh(x), x * y);
}
// small x: normal case
if (ix < 0x40360000) {
return Complex(f64).new(math.cosh(x) * math.cos(y), math.sinh(x) * math.sin(y));
}
// |x|>= 22, so cosh(x) ~= exp(|x|)
if (ix < 0x40862e42) {
// x < 710: exp(|x|) won't overflow
const h = math.exp(math.fabs(x)) * 0.5;
return Complex(f64).new(h * math.cos(y), math.copysign(f64, h, x) * math.sin(y));
}
// x < 1455: scale to avoid overflow
else if (ix < 0x4096bbaa) {
const v = Complex(f64).new(math.fabs(x), y);
const r = ldexp_cexp(v, -1);
return Complex(f64).new(x, y * math.copysign(f64, 1, x));
}
// x >= 1455: result always overflows
else {
const h = 0x1p1023;
return Complex(f64).new(h * h * math.cos(y), h * math.sin(y));
}
}
if (ix | lx == 0 and iy >= 0x7ff00000) {
return Complex(f64).new(y - y, math.copysign(f64, 0, x * (y - y)));
}
if (iy | ly == 0 and ix >= 0x7ff00000) {
if ((hx & 0xfffff) | lx == 0) {
return Complex(f64).new(x * x, math.copysign(f64, 0, x) * y);
}
return Complex(f64).new(x * x, math.copysign(f64, 0, (x + x) * y));
}
if (ix < 0x7ff00000 and iy >= 0x7ff00000) {
return Complex(f64).new(y - y, x * (y - y));
}
if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) {
if (iy >= 0x7ff00000) {
return Complex(f64).new(x * x, x * (y - y));
}
return Complex(f64).new(x * x * math.cos(y), x * math.sin(y));
}
return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y));
}
const epsilon = 0.0001;
test "complex.ccosh32" {
const a = Complex(f32).new(5, 3);
const c = cosh(a);
debug.assert(math.approxEq(f32, c.re, -73.467300, epsilon));
debug.assert(math.approxEq(f32, c.im, 10.471557, epsilon));
}
test "complex.ccosh64" {
const a = Complex(f64).new(5, 3);
const c = cosh(a);
debug.assert(math.approxEq(f64, c.re, -73.467300, epsilon));
debug.assert(math.approxEq(f64, c.im, 10.471557, epsilon));
}

140
std/math/complex/exp.zig Normal file
View File

@ -0,0 +1,140 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
const ldexp_cexp = @import("ldexp.zig").ldexp_cexp;
pub fn exp(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => exp32(z),
f64 => exp64(z),
else => @compileError("exp not implemented for " ++ @typeName(z)),
};
}
fn exp32(z: &const Complex(f32)) Complex(f32) {
@setFloatMode(this, @import("builtin").FloatMode.Strict);
const exp_overflow = 0x42b17218; // max_exp * ln2 ~= 88.72283955
const cexp_overflow = 0x43400074; // (max_exp - min_denom_exp) * ln2
const x = z.re;
const y = z.im;
const hy = @bitCast(u32, y) & 0x7fffffff;
// cexp(x + i0) = exp(x) + i0
if (hy == 0) {
return Complex(f32).new(math.exp(x), y);
}
const hx = @bitCast(u32, x);
// cexp(0 + iy) = cos(y) + isin(y)
if ((hx & 0x7fffffff) == 0) {
return Complex(f32).new(math.cos(y), math.sin(y));
}
if (hy >= 0x7f800000) {
// cexp(finite|nan +- i inf|nan) = nan + i nan
if ((hx & 0x7fffffff) != 0x7f800000) {
return Complex(f32).new(y - y, y - y);
}
// cexp(-inf +- i inf|nan) = 0 + i0
else if (hx & 0x80000000 != 0) {
return Complex(f32).new(0, 0);
}
// cexp(+inf +- i inf|nan) = inf + i nan
else {
return Complex(f32).new(x, y - y);
}
}
// 88.7 <= x <= 192 so must scale
if (hx >= exp_overflow and hx <= cexp_overflow) {
return ldexp_cexp(z, 0);
}
// - x < exp_overflow => exp(x) won't overflow (common)
// - x > cexp_overflow, so exp(x) * s overflows for s > 0
// - x = +-inf
// - x = nan
else {
const exp_x = math.exp(x);
return Complex(f32).new(exp_x * math.cos(y), exp_x * math.sin(y));
}
}
fn exp64(z: &const Complex(f64)) Complex(f64) {
const exp_overflow = 0x40862e42; // high bits of max_exp * ln2 ~= 710
const cexp_overflow = 0x4096b8e4; // (max_exp - min_denorm_exp) * ln2
const x = z.re;
const y = z.im;
const fy = @bitCast(u64, y);
const hy = u32(fy >> 32) & 0x7fffffff;
const ly = @truncate(u32, fy);
// cexp(x + i0) = exp(x) + i0
if (hy | ly == 0) {
return Complex(f64).new(math.exp(x), y);
}
const fx = @bitCast(u64, x);
const hx = u32(fx >> 32);
const lx = @truncate(u32, fx);
// cexp(0 + iy) = cos(y) + isin(y)
if ((hx & 0x7fffffff) | lx == 0) {
return Complex(f64).new(math.cos(y), math.sin(y));
}
if (hy >= 0x7ff00000) {
// cexp(finite|nan +- i inf|nan) = nan + i nan
if (lx != 0 or (hx & 0x7fffffff) != 0x7ff00000) {
return Complex(f64).new(y - y, y - y);
}
// cexp(-inf +- i inf|nan) = 0 + i0
else if (hx & 0x80000000 != 0) {
return Complex(f64).new(0, 0);
}
// cexp(+inf +- i inf|nan) = inf + i nan
else {
return Complex(f64).new(x, y - y);
}
}
// 709.7 <= x <= 1454.3 so must scale
if (hx >= exp_overflow and hx <= cexp_overflow) {
const r = ldexp_cexp(z, 0);
return *r;
}
// - x < exp_overflow => exp(x) won't overflow (common)
// - x > cexp_overflow, so exp(x) * s overflows for s > 0
// - x = +-inf
// - x = nan
else {
const exp_x = math.exp(x);
return Complex(f64).new(exp_x * math.cos(y), exp_x * math.sin(y));
}
}
const epsilon = 0.0001;
test "complex.cexp32" {
const a = Complex(f32).new(5, 3);
const c = exp(a);
debug.assert(math.approxEq(f32, c.re, -146.927917, epsilon));
debug.assert(math.approxEq(f32, c.im, 20.944065, epsilon));
}
test "complex.cexp64" {
const a = Complex(f32).new(5, 3);
const c = exp(a);
debug.assert(math.approxEq(f64, c.re, -146.927917, epsilon));
debug.assert(math.approxEq(f64, c.im, 20.944065, epsilon));
}

171
std/math/complex/index.zig Normal file
View File

@ -0,0 +1,171 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
pub const abs = @import("abs.zig").abs;
pub const acosh = @import("acosh.zig").acosh;
pub const acos = @import("acos.zig").acos;
pub const arg = @import("arg.zig").arg;
pub const asinh = @import("asinh.zig").asinh;
pub const asin = @import("asin.zig").asin;
pub const atanh = @import("atanh.zig").atanh;
pub const atan = @import("atan.zig").atan;
pub const conj = @import("conj.zig").conj;
pub const cosh = @import("cosh.zig").cosh;
pub const cos = @import("cos.zig").cos;
pub const exp = @import("exp.zig").exp;
pub const log = @import("log.zig").log;
pub const pow = @import("pow.zig").pow;
pub const proj = @import("proj.zig").proj;
pub const sinh = @import("sinh.zig").sinh;
pub const sin = @import("sin.zig").sin;
pub const sqrt = @import("sqrt.zig").sqrt;
pub const tanh = @import("tanh.zig").tanh;
pub const tan = @import("tan.zig").tan;
pub fn Complex(comptime T: type) type {
return struct {
const Self = this;
re: T,
im: T,
pub fn new(re: T, im: T) Self {
return Self {
.re = re,
.im = im,
};
}
pub fn add(self: &const Self, other: &const Self) Self {
return Self {
.re = self.re + other.re,
.im = self.im + other.im,
};
}
pub fn sub(self: &const Self, other: &const Self) Self {
return Self {
.re = self.re - other.re,
.im = self.im - other.im,
};
}
pub fn mul(self: &const Self, other: &const Self) Self {
return Self {
.re = self.re * other.re - self.im * other.im,
.im = self.im * other.re + self.re * other.im,
};
}
pub fn div(self: &const Self, other: &const Self) Self {
const re_num = self.re * other.re + self.im * other.im;
const im_num = self.im * other.re - self.re * other.im;
const den = other.re * other.re + other.im * other.im;
return Self {
.re = re_num / den,
.im = im_num / den,
};
}
pub fn conjugate(self: &const Self) Self {
return Self {
.re = self.re,
.im = -self.im,
};
}
pub fn reciprocal(self: &const Self) Self {
const m = self.re * self.re + self.im * self.im;
return Self {
.re = self.re / m,
.im = -self.im / m,
};
}
pub fn magnitude(self: &const Self) T {
return math.sqrt(self.re * self.re + self.im * self.im);
}
};
}
const epsilon = 0.0001;
test "complex.add" {
const a = Complex(f32).new(5, 3);
const b = Complex(f32).new(2, 7);
const c = a.add(b);
debug.assert(c.re == 7 and c.im == 10);
}
test "complex.sub" {
const a = Complex(f32).new(5, 3);
const b = Complex(f32).new(2, 7);
const c = a.sub(b);
debug.assert(c.re == 3 and c.im == -4);
}
test "complex.mul" {
const a = Complex(f32).new(5, 3);
const b = Complex(f32).new(2, 7);
const c = a.mul(b);
debug.assert(c.re == -11 and c.im == 41);
}
test "complex.div" {
const a = Complex(f32).new(5, 3);
const b = Complex(f32).new(2, 7);
const c = a.div(b);
debug.assert(math.approxEq(f32, c.re, f32(31)/53, epsilon) and
math.approxEq(f32, c.im, f32(-29)/53, epsilon));
}
test "complex.conjugate" {
const a = Complex(f32).new(5, 3);
const c = a.conjugate();
debug.assert(c.re == 5 and c.im == -3);
}
test "complex.reciprocal" {
const a = Complex(f32).new(5, 3);
const c = a.reciprocal();
debug.assert(math.approxEq(f32, c.re, f32(5)/34, epsilon) and
math.approxEq(f32, c.im, f32(-3)/34, epsilon));
}
test "complex.magnitude" {
const a = Complex(f32).new(5, 3);
const c = a.magnitude();
debug.assert(math.approxEq(f32, c, 5.83095, epsilon));
}
test "complex.cmath" {
_ = @import("abs.zig");
_ = @import("acosh.zig");
_ = @import("acos.zig");
_ = @import("arg.zig");
_ = @import("asinh.zig");
_ = @import("asin.zig");
_ = @import("atanh.zig");
_ = @import("atan.zig");
_ = @import("conj.zig");
_ = @import("cosh.zig");
_ = @import("cos.zig");
_ = @import("exp.zig");
_ = @import("log.zig");
_ = @import("pow.zig");
_ = @import("proj.zig");
_ = @import("sinh.zig");
_ = @import("sin.zig");
_ = @import("sqrt.zig");
_ = @import("tanh.zig");
_ = @import("tan.zig");
}

View File

@ -0,0 +1,75 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn ldexp_cexp(z: var, expt: i32) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => ldexp_cexp32(z, expt),
f64 => ldexp_cexp64(z, expt),
else => unreachable,
};
}
fn frexp_exp32(x: f32, expt: &i32) f32 {
const k = 235; // reduction constant
const kln2 = 162.88958740; // k * ln2
const exp_x = math.exp(x - kln2);
const hx = @bitCast(u32, exp_x);
*expt = i32(hx >> 23) - (0x7f + 127) + k;
return @bitCast(f32, (hx & 0x7fffff) | ((0x7f + 127) << 23));
}
fn ldexp_cexp32(z: &const Complex(f32), expt: i32) Complex(f32) {
var ex_expt: i32 = undefined;
const exp_x = frexp_exp32(z.re, &ex_expt);
const exptf = expt + ex_expt;
const half_expt1 = @divTrunc(exptf, 2);
const scale1 = @bitCast(f32, (0x7f + half_expt1) << 23);
const half_expt2 = exptf - half_expt1;
const scale2 = @bitCast(f32, (0x7f + half_expt2) << 23);
return Complex(f32).new(
math.cos(z.im) * exp_x * scale1 * scale2,
math.sin(z.im) * exp_x * scale1 * scale2,
);
}
fn frexp_exp64(x: f64, expt: &i32) f64 {
const k = 1799; // reduction constant
const kln2 = 1246.97177782734161156; // k * ln2
const exp_x = math.exp(x - kln2);
const fx = @bitCast(u64, x);
const hx = u32(fx >> 32);
const lx = @truncate(u32, fx);
*expt = i32(hx >> 20) - (0x3ff + 1023) + k;
const high_word = (hx & 0xfffff) | ((0x3ff + 1023) << 20);
return @bitCast(f64, (u64(high_word) << 32) | lx);
}
fn ldexp_cexp64(z: &const Complex(f64), expt: i32) Complex(f64) {
var ex_expt: i32 = undefined;
const exp_x = frexp_exp64(z.re, &ex_expt);
const exptf = i64(expt + ex_expt);
const half_expt1 = @divTrunc(exptf, 2);
const scale1 = @bitCast(f64, (0x3ff + half_expt1) << 20);
const half_expt2 = exptf - half_expt1;
const scale2 = @bitCast(f64, (0x3ff + half_expt2) << 20);
return Complex(f64).new(
math.cos(z.im) * exp_x * scale1 * scale2,
math.sin(z.im) * exp_x * scale1 * scale2,
);
}

23
std/math/complex/log.zig Normal file
View File

@ -0,0 +1,23 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn log(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const r = cmath.abs(z);
const phi = cmath.arg(z);
return Complex(T).new(math.ln(r), phi);
}
const epsilon = 0.0001;
test "complex.clog" {
const a = Complex(f32).new(5, 3);
const c = log(a);
debug.assert(math.approxEq(f32, c.re, 1.763180, epsilon));
debug.assert(math.approxEq(f32, c.im, 0.540419, epsilon));
}

22
std/math/complex/pow.zig Normal file
View File

@ -0,0 +1,22 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn pow(comptime T: type, z: &const T, c: &const T) T {
const p = cmath.log(z);
const q = c.mul(p);
return cmath.exp(q);
}
const epsilon = 0.0001;
test "complex.cpow" {
const a = Complex(f32).new(5, 3);
const b = Complex(f32).new(2.3, -1.3);
const c = pow(Complex(f32), a, b);
debug.assert(math.approxEq(f32, c.re, 58.049110, epsilon));
debug.assert(math.approxEq(f32, c.im, -101.003433, epsilon));
}

24
std/math/complex/proj.zig Normal file
View File

@ -0,0 +1,24 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn proj(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
if (math.isInf(z.re) or math.isInf(z.im)) {
return Complex(T).new(math.inf(T), math.copysign(T, 0, z.re));
}
return Complex(T).new(z.re, z.im);
}
const epsilon = 0.0001;
test "complex.cproj" {
const a = Complex(f32).new(5, 3);
const c = proj(a);
debug.assert(c.re == 5 and c.im == 3);
}

22
std/math/complex/sin.zig Normal file
View File

@ -0,0 +1,22 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn sin(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const p = Complex(T).new(-z.im, z.re);
const q = cmath.sinh(p);
return Complex(T).new(q.im, -q.re);
}
const epsilon = 0.0001;
test "complex.csin" {
const a = Complex(f32).new(5, 3);
const c = sin(a);
debug.assert(math.approxEq(f32, c.re, -9.654126, epsilon));
debug.assert(math.approxEq(f32, c.im, 2.841692, epsilon));
}

164
std/math/complex/sinh.zig Normal file
View File

@ -0,0 +1,164 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
const ldexp_cexp = @import("ldexp.zig").ldexp_cexp;
pub fn sinh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => sinh32(z),
f64 => sinh64(z),
else => @compileError("tan not implemented for " ++ @typeName(z)),
};
}
fn sinh32(z: &const Complex(f32)) Complex(f32) {
const x = z.re;
const y = z.im;
const hx = @bitCast(u32, x);
const ix = hx & 0x7fffffff;
const hy = @bitCast(u32, y);
const iy = hy & 0x7fffffff;
if (ix < 0x7f800000 and iy < 0x7f800000) {
if (iy == 0) {
return Complex(f32).new(math.sinh(x), y);
}
// small x: normal case
if (ix < 0x41100000) {
return Complex(f32).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y));
}
// |x|>= 9, so cosh(x) ~= exp(|x|)
if (ix < 0x42b17218) {
// x < 88.7: exp(|x|) won't overflow
const h = math.exp(math.fabs(x)) * 0.5;
return Complex(f32).new(math.copysign(f32, h, x) * math.cos(y), h * math.sin(y));
}
// x < 192.7: scale to avoid overflow
else if (ix < 0x4340b1e7) {
const v = Complex(f32).new(math.fabs(x), y);
const r = ldexp_cexp(v, -1);
return Complex(f32).new(x * math.copysign(f32, 1, x), y);
}
// x >= 192.7: result always overflows
else {
const h = 0x1p127 * x;
return Complex(f32).new(h * math.cos(y), h * h * math.sin(y));
}
}
if (ix == 0 and iy >= 0x7f800000) {
return Complex(f32).new(math.copysign(f32, 0, x * (y - y)), y - y);
}
if (iy == 0 and ix >= 0x7f800000) {
if (hx & 0x7fffff == 0) {
return Complex(f32).new(x, y);
}
return Complex(f32).new(x, math.copysign(f32, 0, y));
}
if (ix < 0x7f800000 and iy >= 0x7f800000) {
return Complex(f32).new(y - y, x * (y - y));
}
if (ix >= 0x7f800000 and (hx & 0x7fffff) == 0) {
if (iy >= 0x7f800000) {
return Complex(f32).new(x * x, x * (y - y));
}
return Complex(f32).new(x * math.cos(y), math.inf_f32 * math.sin(y));
}
return Complex(f32).new((x * x) * (y - y), (x + x) * (y - y));
}
fn sinh64(z: &const Complex(f64)) Complex(f64) {
const x = z.re;
const y = z.im;
const fx = @bitCast(u64, x);
const hx = u32(fx >> 32);
const lx = @truncate(u32, fx);
const ix = hx & 0x7fffffff;
const fy = @bitCast(u64, y);
const hy = u32(fy >> 32);
const ly = @truncate(u32, fy);
const iy = hy & 0x7fffffff;
if (ix < 0x7ff00000 and iy < 0x7ff00000) {
if (iy | ly == 0) {
return Complex(f64).new(math.sinh(x), y);
}
// small x: normal case
if (ix < 0x40360000) {
return Complex(f64).new(math.sinh(x) * math.cos(y), math.cosh(x) * math.sin(y));
}
// |x|>= 22, so cosh(x) ~= exp(|x|)
if (ix < 0x40862e42) {
// x < 710: exp(|x|) won't overflow
const h = math.exp(math.fabs(x)) * 0.5;
return Complex(f64).new(math.copysign(f64, h, x) * math.cos(y), h * math.sin(y));
}
// x < 1455: scale to avoid overflow
else if (ix < 0x4096bbaa) {
const v = Complex(f64).new(math.fabs(x), y);
const r = ldexp_cexp(v, -1);
return Complex(f64).new(x * math.copysign(f64, 1, x), y);
}
// x >= 1455: result always overflows
else {
const h = 0x1p1023 * x;
return Complex(f64).new(h * math.cos(y), h * h * math.sin(y));
}
}
if (ix | lx == 0 and iy >= 0x7ff00000) {
return Complex(f64).new(math.copysign(f64, 0, x * (y - y)), y - y);
}
if (iy | ly == 0 and ix >= 0x7ff00000) {
if ((hx & 0xfffff) | lx == 0) {
return Complex(f64).new(x, y);
}
return Complex(f64).new(x, math.copysign(f64, 0, y));
}
if (ix < 0x7ff00000 and iy >= 0x7ff00000) {
return Complex(f64).new(y - y, x * (y - y));
}
if (ix >= 0x7ff00000 and (hx & 0xfffff) | lx == 0) {
if (iy >= 0x7ff00000) {
return Complex(f64).new(x * x, x * (y - y));
}
return Complex(f64).new(x * math.cos(y), math.inf_f64 * math.sin(y));
}
return Complex(f64).new((x * x) * (y - y), (x + x) * (y - y));
}
const epsilon = 0.0001;
test "complex.csinh32" {
const a = Complex(f32).new(5, 3);
const c = sinh(a);
debug.assert(math.approxEq(f32, c.re, -73.460617, epsilon));
debug.assert(math.approxEq(f32, c.im, 10.472508, epsilon));
}
test "complex.csinh64" {
const a = Complex(f64).new(5, 3);
const c = sinh(a);
debug.assert(math.approxEq(f64, c.re, -73.460617, epsilon));
debug.assert(math.approxEq(f64, c.im, 10.472508, epsilon));
}

133
std/math/complex/sqrt.zig Normal file
View File

@ -0,0 +1,133 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
// TODO when #733 is solved this can be @typeOf(z) instead of Complex(@typeOf(z.re))
pub fn sqrt(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => sqrt32(z),
f64 => sqrt64(z),
else => @compileError("sqrt not implemented for " ++ @typeName(z)),
};
}
fn sqrt32(z: &const Complex(f32)) Complex(f32) {
const x = z.re;
const y = z.im;
if (x == 0 and y == 0) {
return Complex(f32).new(0, y);
}
if (math.isInf(y)) {
return Complex(f32).new(math.inf(f32), y);
}
if (math.isNan(x)) {
// raise invalid if y is not nan
const t = (y - y) / (y - y);
return Complex(f32).new(x, t);
}
if (math.isInf(x)) {
// sqrt(inf + i nan) = inf + nan i
// sqrt(inf + iy) = inf + i0
// sqrt(-inf + i nan) = nan +- inf i
// sqrt(-inf + iy) = 0 + inf i
if (math.signbit(x)) {
return Complex(f32).new(math.fabs(x - y), math.copysign(f32, x, y));
} else {
return Complex(f32).new(x, math.copysign(f32, y - y, y));
}
}
// y = nan special case is handled fine below
// double-precision avoids overflow with correct rounding.
const dx = f64(x);
const dy = f64(y);
if (dx >= 0) {
const t = math.sqrt((dx + math.hypot(f64, dx, dy)) * 0.5);
return Complex(f32).new(f32(t), f32(dy / (2.0 * t)));
} else {
const t = math.sqrt((-dx + math.hypot(f64, dx, dy)) * 0.5);
return Complex(f32).new(f32(math.fabs(y) / (2.0 * t)), f32(math.copysign(f64, t, y)));
}
}
fn sqrt64(z: &const Complex(f64)) Complex(f64) {
// may encounter overflow for im,re >= DBL_MAX / (1 + sqrt(2))
const threshold = 0x1.a827999fcef32p+1022;
var x = z.re;
var y = z.im;
if (x == 0 and y == 0) {
return Complex(f64).new(0, y);
}
if (math.isInf(y)) {
return Complex(f64).new(math.inf(f64), y);
}
if (math.isNan(x)) {
// raise invalid if y is not nan
const t = (y - y) / (y - y);
return Complex(f64).new(x, t);
}
if (math.isInf(x)) {
// sqrt(inf + i nan) = inf + nan i
// sqrt(inf + iy) = inf + i0
// sqrt(-inf + i nan) = nan +- inf i
// sqrt(-inf + iy) = 0 + inf i
if (math.signbit(x)) {
return Complex(f64).new(math.fabs(x - y), math.copysign(f64, x, y));
} else {
return Complex(f64).new(x, math.copysign(f64, y - y, y));
}
}
// y = nan special case is handled fine below
// scale to avoid overflow
var scale = false;
if (math.fabs(x) >= threshold or math.fabs(y) >= threshold) {
x *= 0.25;
y *= 0.25;
scale = true;
}
var result: Complex(f64) = undefined;
if (x >= 0) {
const t = math.sqrt((x + math.hypot(f64, x, y)) * 0.5);
result = Complex(f64).new(t, y / (2.0 * t));
} else {
const t = math.sqrt((-x + math.hypot(f64, x, y)) * 0.5);
result = Complex(f64).new(math.fabs(y) / (2.0 * t), math.copysign(f64, t, y));
}
if (scale) {
result.re *= 2;
result.im *= 2;
}
return result;
}
const epsilon = 0.0001;
test "complex.csqrt32" {
const a = Complex(f32).new(5, 3);
const c = sqrt(a);
debug.assert(math.approxEq(f32, c.re, 2.327117, epsilon));
debug.assert(math.approxEq(f32, c.im, 0.644574, epsilon));
}
test "complex.csqrt64" {
const a = Complex(f64).new(5, 3);
const c = sqrt(a);
debug.assert(math.approxEq(f64, c.re, 2.3271175190399496, epsilon));
debug.assert(math.approxEq(f64, c.im, 0.6445742373246469, epsilon));
}

22
std/math/complex/tan.zig Normal file
View File

@ -0,0 +1,22 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn tan(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
const q = Complex(T).new(-z.im, z.re);
const r = cmath.tanh(q);
return Complex(T).new(r.im, -r.re);
}
const epsilon = 0.0001;
test "complex.ctan" {
const a = Complex(f32).new(5, 3);
const c = tan(a);
debug.assert(math.approxEq(f32, c.re, -0.002708233, epsilon));
debug.assert(math.approxEq(f32, c.im, 1.004165, epsilon));
}

111
std/math/complex/tanh.zig Normal file
View File

@ -0,0 +1,111 @@
const std = @import("../../index.zig");
const debug = std.debug;
const math = std.math;
const cmath = math.complex;
const Complex = cmath.Complex;
pub fn tanh(z: var) Complex(@typeOf(z.re)) {
const T = @typeOf(z.re);
return switch (T) {
f32 => tanh32(z),
f64 => tanh64(z),
else => @compileError("tan not implemented for " ++ @typeName(z)),
};
}
fn tanh32(z: &const Complex(f32)) Complex(f32) {
const x = z.re;
const y = z.im;
const hx = @bitCast(u32, x);
const ix = hx & 0x7fffffff;
if (ix >= 0x7f800000) {
if (ix & 0x7fffff != 0) {
const r = if (y == 0) y else x * y;
return Complex(f32).new(x, r);
}
const xx = @bitCast(f32, hx - 0x40000000);
const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y);
return Complex(f32).new(xx, math.copysign(f32, 0, r));
}
if (!math.isFinite(y)) {
const r = if (ix != 0) y - y else x;
return Complex(f32).new(r, y - y);
}
// x >= 11
if (ix >= 0x41300000) {
const exp_mx = math.exp(-math.fabs(x));
return Complex(f32).new(math.copysign(f32, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx);
}
// Kahan's algorithm
const t = math.tan(y);
const beta = 1.0 + t * t;
const s = math.sinh(x);
const rho = math.sqrt(1 + s * s);
const den = 1 + beta * s * s;
return Complex(f32).new((beta * rho * s) / den, t / den);
}
fn tanh64(z: &const Complex(f64)) Complex(f64) {
const x = z.re;
const y = z.im;
const fx = @bitCast(u64, x);
const hx = u32(fx >> 32);
const lx = @truncate(u32, fx);
const ix = hx & 0x7fffffff;
if (ix >= 0x7ff00000) {
if ((ix & 0x7fffff) | lx != 0) {
const r = if (y == 0) y else x * y;
return Complex(f64).new(x, r);
}
const xx = @bitCast(f64, (u64(hx - 0x40000000) << 32) | lx);
const r = if (math.isInf(y)) y else math.sin(y) * math.cos(y);
return Complex(f64).new(xx, math.copysign(f64, 0, r));
}
if (!math.isFinite(y)) {
const r = if (ix != 0) y - y else x;
return Complex(f64).new(r, y - y);
}
// x >= 22
if (ix >= 0x40360000) {
const exp_mx = math.exp(-math.fabs(x));
return Complex(f64).new(math.copysign(f64, 1, x), 4 * math.sin(y) * math.cos(y) * exp_mx * exp_mx);
}
// Kahan's algorithm
const t = math.tan(y);
const beta = 1.0 + t * t;
const s = math.sinh(x);
const rho = math.sqrt(1 + s * s);
const den = 1 + beta * s * s;
return Complex(f64).new((beta * rho * s) / den, t / den);
}
const epsilon = 0.0001;
test "complex.ctanh32" {
const a = Complex(f32).new(5, 3);
const c = tanh(a);
debug.assert(math.approxEq(f32, c.re, 0.999913, epsilon));
debug.assert(math.approxEq(f32, c.im, -0.000025, epsilon));
}
test "complex.ctanh64" {
const a = Complex(f64).new(5, 3);
const c = tanh(a);
debug.assert(math.approxEq(f64, c.re, 0.999913, epsilon));
debug.assert(math.approxEq(f64, c.im, -0.000025, epsilon));
}

View File

@ -129,6 +129,9 @@ pub const cos = @import("cos.zig").cos;
pub const sin = @import("sin.zig").sin;
pub const tan = @import("tan.zig").tan;
pub const complex = @import("complex/index.zig");
pub const Complex = complex.Complex;
test "math" {
_ = @import("nan.zig");
_ = @import("isnan.zig");
@ -172,6 +175,8 @@ test "math" {
_ = @import("sin.zig");
_ = @import("cos.zig");
_ = @import("tan.zig");
_ = @import("complex/index.zig");
}

View File

@ -32,10 +32,25 @@ pub const Allocator = struct {
freeFn: fn (self: &Allocator, old_mem: []u8) void,
fn create(self: &Allocator, comptime T: type) !&T {
if (@sizeOf(T) == 0) return &{};
const slice = try self.alloc(T, 1);
return &slice[0];
}
// TODO once #733 is solved, this will replace create
fn construct(self: &Allocator, init: var) t: {
// TODO this is a workaround for type getting parsed as Error!&const T
const T = @typeOf(init).Child;
break :t Error!&T;
} {
const T = @typeOf(init).Child;
if (@sizeOf(T) == 0) return &{};
const slice = try self.alloc(T, 1);
const ptr = &slice[0];
*ptr = *init;
return ptr;
}
fn destroy(self: &Allocator, ptr: var) void {
self.free(ptr[0..1]);
}
@ -53,7 +68,7 @@ pub const Allocator = struct {
const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;
const byte_slice = try self.allocFn(self, byte_count, alignment);
assert(byte_slice.len == byte_count);
// This loop should get optimized out in ReleaseFast mode
// This loop gets optimized out in ReleaseFast mode
for (byte_slice) |*byte| {
*byte = undefined;
}
@ -80,7 +95,7 @@ pub const Allocator = struct {
const byte_slice = try self.reallocFn(self, old_byte_slice, byte_count, alignment);
assert(byte_slice.len == byte_count);
if (n > old_mem.len) {
// This loop should get optimized out in ReleaseFast mode
// This loop gets optimized out in ReleaseFast mode
for (byte_slice[old_byte_slice.len..]) |*byte| {
*byte = undefined;
}
@ -174,6 +189,20 @@ pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) ![]T {
return new_buf;
}
/// Remove values from the beginning of a slice.
pub fn trimLeft(comptime T: type, slice: []const T, values_to_strip: []const T) []const T {
var begin: usize = 0;
while (begin < slice.len and indexOfScalar(T, values_to_strip, slice[begin]) != null) : (begin += 1) {}
return slice[begin..];
}
/// Remove values from the end of a slice.
pub fn trimRight(comptime T: type, slice: []const T, values_to_strip: []const T) []const T {
var end: usize = slice.len;
while (end > 0 and indexOfScalar(T, values_to_strip, slice[end - 1]) != null) : (end -= 1) {}
return slice[0..end];
}
/// Remove values from the beginning and end of a slice.
pub fn trim(comptime T: type, slice: []const T, values_to_strip: []const T) []const T {
var begin: usize = 0;
@ -184,6 +213,8 @@ pub fn trim(comptime T: type, slice: []const T, values_to_strip: []const T) []co
}
test "mem.trim" {
assert(eql(u8, trimLeft(u8, " foo\n ", " \n"), "foo\n "));
assert(eql(u8, trimRight(u8, " foo\n ", " \n"), " foo"));
assert(eql(u8, trim(u8, " foo\n ", " \n"), "foo"));
assert(eql(u8, trim(u8, "foo", " \n"), "foo"));
}
@ -193,6 +224,17 @@ pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) ?usize {
return indexOfScalarPos(T, slice, 0, value);
}
/// Linear search for the last index of a scalar value inside a slice.
pub fn lastIndexOfScalar(comptime T: type, slice: []const T, value: T) ?usize {
var i: usize = slice.len;
while (i != 0) {
i -= 1;
if (slice[i] == value)
return i;
}
return null;
}
pub fn indexOfScalarPos(comptime T: type, slice: []const T, start_index: usize, value: T) ?usize {
var i: usize = start_index;
while (i < slice.len) : (i += 1) {
@ -206,6 +248,18 @@ pub fn indexOfAny(comptime T: type, slice: []const T, values: []const T) ?usize
return indexOfAnyPos(T, slice, 0, values);
}
pub fn lastIndexOfAny(comptime T: type, slice: []const T, values: []const T) ?usize {
var i: usize = slice.len;
while (i != 0) {
i -= 1;
for (values) |value| {
if (slice[i] == value)
return i;
}
}
return null;
}
pub fn indexOfAnyPos(comptime T: type, slice: []const T, start_index: usize, values: []const T) ?usize {
var i: usize = start_index;
while (i < slice.len) : (i += 1) {
@ -221,6 +275,22 @@ pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) ?usize
return indexOfPos(T, haystack, 0, needle);
}
/// Find the index in a slice of a sub-slice, searching from the end backwards.
/// To start looking at a different index, slice the haystack first.
/// TODO is there even a better algorithm for this?
pub fn lastIndexOf(comptime T: type, haystack: []const T, needle: []const T) ?usize {
if (needle.len > haystack.len)
return null;
var i: usize = haystack.len - needle.len;
while (true) : (i -= 1) {
if (mem.eql(T, haystack[i..i+needle.len], needle))
return i;
if (i == 0)
return null;
}
}
// TODO boyer-moore algorithm
pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, needle: []const T) ?usize {
if (needle.len > haystack.len)
@ -237,9 +307,19 @@ pub fn indexOfPos(comptime T: type, haystack: []const T, start_index: usize, nee
test "mem.indexOf" {
assert(??indexOf(u8, "one two three four", "four") == 14);
assert(??lastIndexOf(u8, "one two three two four", "two") == 14);
assert(indexOf(u8, "one two three four", "gour") == null);
assert(lastIndexOf(u8, "one two three four", "gour") == null);
assert(??indexOf(u8, "foo", "foo") == 0);
assert(??lastIndexOf(u8, "foo", "foo") == 0);
assert(indexOf(u8, "foo", "fool") == null);
assert(lastIndexOf(u8, "foo", "lfoo") == null);
assert(lastIndexOf(u8, "foo", "fool") == null);
assert(??indexOf(u8, "foo foo", "foo") == 0);
assert(??lastIndexOf(u8, "foo foo", "foo") == 4);
assert(??lastIndexOfAny(u8, "boo, cat", "abo") == 6);
assert(??lastIndexOfScalar(u8, "boo", 'o') == 2);
}
/// Reads an integer from memory with size equal to bytes.len.
@ -359,6 +439,21 @@ pub fn startsWith(comptime T: type, haystack: []const T, needle: []const T) bool
return if (needle.len > haystack.len) false else eql(T, haystack[0 .. needle.len], needle);
}
test "mem.startsWith" {
assert(startsWith(u8, "Bob", "Bo"));
assert(!startsWith(u8, "Needle in haystack", "haystack"));
}
pub fn endsWith(comptime T: type, haystack: []const T, needle: []const T) bool {
return if (needle.len > haystack.len) false else eql(T, haystack[haystack.len - needle.len ..], needle);
}
test "mem.endsWith" {
assert(endsWith(u8, "Needle in haystack", "haystack"));
assert(!endsWith(u8, "Bob", "Bo"));
}
pub const SplitIterator = struct {
buffer: []const u8,
split_bytes: []const u8,

View File

@ -184,7 +184,7 @@ pub fn write(fd: i32, buf: &const u8, nbyte: usize) usize {
return errnoWrap(c.write(fd, @ptrCast(&const c_void, buf), nbyte));
}
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32,
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32,
offset: isize) usize
{
const ptr_result = c.mmap(@ptrCast(&c_void, address), length,
@ -193,8 +193,8 @@ pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32,
return errnoWrap(isize_result);
}
pub fn munmap(address: &u8, length: usize) usize {
return errnoWrap(c.munmap(@ptrCast(&c_void, address), length));
pub fn munmap(address: usize, length: usize) usize {
return errnoWrap(c.munmap(@intToPtr(&c_void, address), length));
}
pub fn unlink(path: &const u8) usize {

View File

@ -2,6 +2,11 @@ const std = @import("../index.zig");
const builtin = @import("builtin");
const Os = builtin.Os;
const is_windows = builtin.os == Os.windows;
const is_posix = switch (builtin.os) {
builtin.Os.linux,
builtin.Os.macosx => true,
else => false,
};
const os = this;
test "std.os" {
@ -22,7 +27,8 @@ pub const linux = @import("linux/index.zig");
pub const zen = @import("zen.zig");
pub const posix = switch (builtin.os) {
Os.linux => linux,
Os.macosx, Os.ios => darwin,
Os.macosx,
Os.ios => darwin,
Os.zen => zen,
else => @compileError("Unsupported OS"),
};
@ -106,7 +112,8 @@ pub fn getRandomBytes(buf: []u8) !void {
}
return;
},
Os.macosx, Os.ios => {
Os.macosx,
Os.ios => {
const fd = try posixOpenC(c"/dev/urandom", posix.O_RDONLY | posix.O_CLOEXEC, 0);
defer close(fd);
@ -130,7 +137,20 @@ pub fn getRandomBytes(buf: []u8) !void {
}
},
Os.zen => {
const randomness = []u8 {42, 1, 7, 12, 22, 17, 99, 16, 26, 87, 41, 45};
const randomness = []u8 {
42,
1,
7,
12,
22,
17,
99,
16,
26,
87,
41,
45,
};
var i: usize = 0;
while (i < buf.len) : (i += 1) {
if (i > randomness.len) return error.Unknown;
@ -155,7 +175,9 @@ pub fn abort() noreturn {
c.abort();
}
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => {
Os.linux,
Os.macosx,
Os.ios => {
_ = posix.raise(posix.SIGABRT);
_ = posix.raise(posix.SIGKILL);
while (true) {}
@ -177,7 +199,9 @@ pub fn exit(status: u8) noreturn {
c.exit(status);
}
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => {
Os.linux,
Os.macosx,
Os.ios => {
posix.exit(status);
},
Os.windows => {
@ -226,12 +250,14 @@ pub fn posixRead(fd: i32, buf: []u8) !void {
if (err > 0) {
return switch (err) {
posix.EINTR => continue,
posix.EINVAL, posix.EFAULT => unreachable,
posix.EINVAL,
posix.EFAULT => unreachable,
posix.EAGAIN => error.WouldBlock,
posix.EBADF => error.FileClosed,
posix.EIO => error.InputOutput,
posix.EISDIR => error.IsDir,
posix.ENOBUFS, posix.ENOMEM => error.SystemResources,
posix.ENOBUFS,
posix.ENOMEM => error.SystemResources,
else => unexpectedErrorPosix(err),
};
}
@ -266,7 +292,8 @@ pub fn posixWrite(fd: i32, bytes: []const u8) !void {
if (write_err > 0) {
return switch (write_err) {
posix.EINTR => continue,
posix.EINVAL, posix.EFAULT => unreachable,
posix.EINVAL,
posix.EFAULT => unreachable,
posix.EAGAIN => PosixWriteError.WouldBlock,
posix.EBADF => PosixWriteError.FileClosed,
posix.EDESTADDRREQ => PosixWriteError.DestinationAddressRequired,
@ -322,7 +349,8 @@ pub fn posixOpenC(file_path: &const u8, flags: u32, perm: usize) !i32 {
posix.EFAULT => unreachable,
posix.EINVAL => unreachable,
posix.EACCES => return PosixOpenError.AccessDenied,
posix.EFBIG, posix.EOVERFLOW => return PosixOpenError.FileTooBig,
posix.EFBIG,
posix.EOVERFLOW => return PosixOpenError.FileTooBig,
posix.EISDIR => return PosixOpenError.IsDir,
posix.ELOOP => return PosixOpenError.SymLinkLoop,
posix.EMFILE => return PosixOpenError.ProcessFdQuotaExceeded,
@ -347,7 +375,8 @@ pub fn posixDup2(old_fd: i32, new_fd: i32) !void {
const err = posix.getErrno(posix.dup2(old_fd, new_fd));
if (err > 0) {
return switch (err) {
posix.EBUSY, posix.EINTR => continue,
posix.EBUSY,
posix.EINTR => continue,
posix.EMFILE => error.ProcessFdQuotaExceeded,
posix.EINVAL => unreachable,
else => unexpectedErrorPosix(err),
@ -393,9 +422,7 @@ pub fn freeNullDelimitedEnvMap(allocator: &Allocator, envp_buf: []?&u8) void {
/// pointers after the args and after the environment variables.
/// `argv[0]` is the executable path.
/// This function also uses the PATH environment variable to get the full path to the executable.
pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap,
allocator: &Allocator) !void
{
pub fn posixExecve(argv: []const []const u8, env_map: &const BufMap, allocator: &Allocator) !void {
const argv_buf = try allocator.alloc(?&u8, argv.len + 1);
mem.set(?&u8, argv_buf, null);
defer {
@ -466,10 +493,17 @@ fn posixExecveErrnoToErr(err: usize) PosixExecveError {
assert(err > 0);
return switch (err) {
posix.EFAULT => unreachable,
posix.E2BIG, posix.EMFILE, posix.ENAMETOOLONG, posix.ENFILE, posix.ENOMEM => error.SystemResources,
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EINVAL, posix.ENOEXEC => error.InvalidExe,
posix.EIO, posix.ELOOP => error.FileSystem,
posix.E2BIG,
posix.EMFILE,
posix.ENAMETOOLONG,
posix.ENFILE,
posix.ENOMEM => error.SystemResources,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EINVAL,
posix.ENOEXEC => error.InvalidExe,
posix.EIO,
posix.ELOOP => error.FileSystem,
posix.EISDIR => error.IsDir,
posix.ENOENT => error.FileNotFound,
posix.ENOTDIR => error.NotDir,
@ -492,8 +526,7 @@ pub fn getEnvMap(allocator: &Allocator) !BufMap {
var i: usize = 0;
while (true) {
if (ptr[i] == 0)
return result;
if (ptr[i] == 0) return result;
const key_start = i;
@ -531,8 +564,7 @@ pub fn getEnvPosix(key: []const u8) ?[]const u8 {
var line_i: usize = 0;
while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
const this_key = ptr[0..line_i];
if (!mem.eql(u8, key, this_key))
continue;
if (!mem.eql(u8, key, this_key)) continue;
var end_i: usize = line_i;
while (ptr[end_i] != 0) : (end_i += 1) {}
@ -685,8 +717,10 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path:
const err = posix.getErrno(posix.symlink(existing_buf.ptr, new_buf.ptr));
if (err > 0) {
return switch (err) {
posix.EFAULT, posix.EINVAL => unreachable,
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EFAULT,
posix.EINVAL => unreachable,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EDQUOT => error.DiskQuota,
posix.EEXIST => error.PathAlreadyExists,
posix.EIO => error.FileSystem,
@ -703,9 +737,7 @@ pub fn symLinkPosix(allocator: &Allocator, existing_path: []const u8, new_path:
}
// here we replace the standard +/ with -_ so that it can be used in a file name
const b64_fs_encoder = base64.Base64Encoder.init(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
base64.standard_pad_char);
const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char);
pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path: []const u8) !void {
if (symLink(allocator, existing_path, new_path)) {
@ -733,7 +765,6 @@ pub fn atomicSymLink(allocator: &Allocator, existing_path: []const u8, new_path:
else => return err, // TODO zig should know this set does not include PathAlreadyExists
}
}
}
pub fn deleteFile(allocator: &Allocator, file_path: []const u8) !void {
@ -756,7 +787,8 @@ pub fn deleteFileWindows(allocator: &Allocator, file_path: []const u8) !void {
return switch (err) {
windows.ERROR.FILE_NOT_FOUND => error.FileNotFound,
windows.ERROR.ACCESS_DENIED => error.AccessDenied,
windows.ERROR.FILENAME_EXCED_RANGE, windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
windows.ERROR.FILENAME_EXCED_RANGE,
windows.ERROR.INVALID_PARAMETER => error.NameTooLong,
else => unexpectedErrorWindows(err),
};
}
@ -772,9 +804,11 @@ pub fn deleteFilePosix(allocator: &Allocator, file_path: []const u8) !void {
const err = posix.getErrno(posix.unlink(buf.ptr));
if (err > 0) {
return switch (err) {
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EBUSY => error.FileBusy,
posix.EFAULT, posix.EINVAL => unreachable,
posix.EFAULT,
posix.EINVAL => unreachable,
posix.EIO => error.FileSystem,
posix.EISDIR => error.IsDir,
posix.ELOOP => error.SymLinkLoop,
@ -914,10 +948,12 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
const err = posix.getErrno(posix.rename(old_buf.ptr, new_buf.ptr));
if (err > 0) {
return switch (err) {
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EBUSY => error.FileBusy,
posix.EDQUOT => error.DiskQuota,
posix.EFAULT, posix.EINVAL => unreachable,
posix.EFAULT,
posix.EINVAL => unreachable,
posix.EISDIR => error.IsDir,
posix.ELOOP => error.SymLinkLoop,
posix.EMLINK => error.LinkQuotaExceeded,
@ -926,7 +962,8 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
posix.ENOTDIR => error.NotDir,
posix.ENOMEM => error.SystemResources,
posix.ENOSPC => error.NoSpaceLeft,
posix.EEXIST, posix.ENOTEMPTY => error.PathAlreadyExists,
posix.EEXIST,
posix.ENOTEMPTY => error.PathAlreadyExists,
posix.EROFS => error.ReadOnlyFileSystem,
posix.EXDEV => error.RenameAcrossMountPoints,
else => unexpectedErrorPosix(err),
@ -964,7 +1001,8 @@ pub fn makeDirPosix(allocator: &Allocator, dir_path: []const u8) !void {
const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
if (err > 0) {
return switch (err) {
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EDQUOT => error.DiskQuota,
posix.EEXIST => error.PathAlreadyExists,
posix.EFAULT => unreachable,
@ -994,27 +1032,23 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) !void {
// TODO stat the file and return an error if it's not a directory
// this is important because otherwise a dangling symlink
// could cause an infinite loop
if (end_index == resolved_path.len)
return;
if (end_index == resolved_path.len) return;
} else if (err == error.FileNotFound) {
// march end_index backward until next path component
while (true) {
end_index -= 1;
if (os.path.isSep(resolved_path[end_index]))
break;
if (os.path.isSep(resolved_path[end_index])) break;
}
continue;
} else {
return err;
}
};
if (end_index == resolved_path.len)
return;
if (end_index == resolved_path.len) return;
// march end_index forward until next path component
while (true) {
end_index += 1;
if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index]))
break;
if (end_index == resolved_path.len or os.path.isSep(resolved_path[end_index])) break;
}
}
}
@ -1031,15 +1065,18 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void {
const err = posix.getErrno(posix.rmdir(path_buf.ptr));
if (err > 0) {
return switch (err) {
posix.EACCES, posix.EPERM => error.AccessDenied,
posix.EACCES,
posix.EPERM => error.AccessDenied,
posix.EBUSY => error.FileBusy,
posix.EFAULT, posix.EINVAL => unreachable,
posix.EFAULT,
posix.EINVAL => unreachable,
posix.ELOOP => error.SymLinkLoop,
posix.ENAMETOOLONG => error.NameTooLong,
posix.ENOENT => error.FileNotFound,
posix.ENOMEM => error.SystemResources,
posix.ENOTDIR => error.NotDir,
posix.EEXIST, posix.ENOTEMPTY => error.DirNotEmpty,
posix.EEXIST,
posix.ENOTEMPTY => error.DirNotEmpty,
posix.EROFS => error.ReadOnlyFileSystem,
else => unexpectedErrorPosix(err),
};
@ -1049,7 +1086,7 @@ pub fn deleteDir(allocator: &Allocator, dir_path: []const u8) !void {
/// Whether ::full_path describes a symlink, file, or directory, this function
/// removes it. If it cannot be removed because it is a non-empty directory,
/// this function recursively removes its entries and then tries again.
// TODO non-recursive implementation
/// TODO non-recursive implementation
const DeleteTreeError = error {
OutOfMemory,
AccessDenied,
@ -1091,8 +1128,7 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!
error.NotDir,
error.FileSystem,
error.FileBusy,
error.Unexpected
=> return err,
error.Unexpected => return err,
}
{
var dir = Dir.open(allocator, full_path) catch |err| switch (err) {
@ -1116,8 +1152,7 @@ pub fn deleteTree(allocator: &Allocator, full_path: []const u8) DeleteTreeError!
error.SystemResources,
error.NoSpaceLeft,
error.PathAlreadyExists,
error.Unexpected
=> return err,
error.Unexpected => return err,
};
defer dir.close();
@ -1147,7 +1182,8 @@ pub const Dir = struct {
end_index: usize,
const darwin_seek_t = switch (builtin.os) {
Os.macosx, Os.ios => i64,
Os.macosx,
Os.ios => i64,
else => void,
};
@ -1172,11 +1208,13 @@ pub const Dir = struct {
const fd = switch (builtin.os) {
Os.windows => @compileError("TODO support Dir.open for windows"),
Os.linux => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_DIRECTORY | posix.O_CLOEXEC, 0),
Os.macosx, Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY|posix.O_NONBLOCK|posix.O_DIRECTORY|posix.O_CLOEXEC, 0),
Os.macosx,
Os.ios => try posixOpen(allocator, dir_path, posix.O_RDONLY | posix.O_NONBLOCK | posix.O_DIRECTORY | posix.O_CLOEXEC, 0),
else => @compileError("Dir.open is not supported for this platform"),
};
const darwin_seek_init = switch (builtin.os) {
Os.macosx, Os.ios => 0,
Os.macosx,
Os.ios => 0,
else => {},
};
return Dir {
@ -1199,7 +1237,8 @@ pub const Dir = struct {
pub fn next(self: &Dir) !?Entry {
switch (builtin.os) {
Os.linux => return self.nextLinux(),
Os.macosx, Os.ios => return self.nextDarwin(),
Os.macosx,
Os.ios => return self.nextDarwin(),
Os.windows => return self.nextWindows(),
else => @compileError("Dir.next not supported on " ++ @tagName(builtin.os)),
}
@ -1213,12 +1252,13 @@ pub const Dir = struct {
}
while (true) {
const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len,
&self.darwin_seek);
const result = posix.getdirentries64(self.fd, self.buf.ptr, self.buf.len, &self.darwin_seek);
const err = posix.getErrno(result);
if (err > 0) {
switch (err) {
posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
posix.EBADF,
posix.EFAULT,
posix.ENOTDIR => unreachable,
posix.EINVAL => {
self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
continue;
@ -1226,8 +1266,7 @@ pub const Dir = struct {
else => return unexpectedErrorPosix(err),
}
}
if (result == 0)
return null;
if (result == 0) return null;
self.index = 0;
self.end_index = result;
break;
@ -1278,7 +1317,9 @@ pub const Dir = struct {
const err = posix.getErrno(result);
if (err > 0) {
switch (err) {
posix.EBADF, posix.EFAULT, posix.ENOTDIR => unreachable,
posix.EBADF,
posix.EFAULT,
posix.ENOTDIR => unreachable,
posix.EINVAL => {
self.buf = try self.allocator.realloc(u8, self.buf, self.buf.len * 2);
continue;
@ -1286,8 +1327,7 @@ pub const Dir = struct {
else => return unexpectedErrorPosix(err),
}
}
if (result == 0)
return null;
if (result == 0) return null;
self.index = 0;
self.end_index = result;
break;
@ -1362,7 +1402,8 @@ pub fn readLink(allocator: &Allocator, pathname: []const u8) ![]u8 {
if (err > 0) {
return switch (err) {
posix.EACCES => error.AccessDenied,
posix.EFAULT, posix.EINVAL => unreachable,
posix.EFAULT,
posix.EINVAL => unreachable,
posix.EIO => error.FileSystem,
posix.ELOOP => error.SymLinkLoop,
posix.ENAMETOOLONG => error.NameTooLong,
@ -1455,8 +1496,7 @@ pub const ArgIteratorPosix = struct {
}
pub fn next(self: &ArgIteratorPosix) ?[]const u8 {
if (self.index == self.count)
return null;
if (self.index == self.count) return null;
const s = raw[self.index];
self.index += 1;
@ -1464,8 +1504,7 @@ pub const ArgIteratorPosix = struct {
}
pub fn skip(self: &ArgIteratorPosix) bool {
if (self.index == self.count)
return false;
if (self.index == self.count) return false;
self.index += 1;
return true;
@ -1483,7 +1522,9 @@ pub const ArgIteratorWindows = struct {
quote_count: usize,
seen_quote_count: usize,
pub const NextError = error{OutOfMemory};
pub const NextError = error {
OutOfMemory,
};
pub fn init() ArgIteratorWindows {
return initWithCmdLine(windows.GetCommandLineA());
@ -1506,7 +1547,8 @@ pub const ArgIteratorWindows = struct {
const byte = self.cmd_line[self.index];
switch (byte) {
0 => return null,
' ', '\t' => continue,
' ',
'\t' => continue,
else => break,
}
}
@ -1520,7 +1562,8 @@ pub const ArgIteratorWindows = struct {
const byte = self.cmd_line[self.index];
switch (byte) {
0 => return false,
' ', '\t' => continue,
' ',
'\t' => continue,
else => break,
}
}
@ -1539,7 +1582,8 @@ pub const ArgIteratorWindows = struct {
'\\' => {
backslash_count += 1;
},
' ', '\t' => {
' ',
'\t' => {
if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) {
return true;
}
@ -1579,7 +1623,8 @@ pub const ArgIteratorWindows = struct {
'\\' => {
backslash_count += 1;
},
' ', '\t' => {
' ',
'\t' => {
try self.emitBackslashes(&buf, backslash_count);
backslash_count = 0;
if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) {
@ -1623,7 +1668,6 @@ pub const ArgIteratorWindows = struct {
}
}
}
};
pub const ArgIterator = struct {
@ -1713,15 +1757,47 @@ pub fn argsFree(allocator: &mem.Allocator, args_alloc: []const []u8) void {
}
test "windows arg parsing" {
testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"});
testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"});
testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{"a\\\\\\b", "de fg", "h"});
testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{"a\\\"b", "c", "d"});
testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{"a\\\\b c", "d", "e"});
testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{"a", "b", "c", "\"d", "f"});
testWindowsCmdLine(c"a b\tc d", [][]const u8 {
"a",
"b",
"c",
"d",
});
testWindowsCmdLine(c"\"abc\" d e", [][]const u8 {
"abc",
"d",
"e",
});
testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8 {
"a\\\\\\b",
"de fg",
"h",
});
testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8 {
"a\\\"b",
"c",
"d",
});
testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8 {
"a\\\\b c",
"d",
"e",
});
testWindowsCmdLine(c"a b\tc \"d f", [][]const u8 {
"a",
"b",
"c",
"\"d",
"f",
});
testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"",
[][]const u8{".\\..\\zig-cache\\build", "bin\\zig.exe", ".\\..", ".\\..\\zig-cache", "--help"});
testWindowsCmdLine(c"\".\\..\\zig-cache\\build\" \"bin\\zig.exe\" \".\\..\" \".\\..\\zig-cache\" \"--help\"", [][]const u8 {
".\\..\\zig-cache\\build",
"bin\\zig.exe",
".\\..",
".\\..\\zig-cache",
"--help",
});
}
fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const u8) void {
@ -1768,7 +1844,8 @@ pub fn openSelfExe() !os.File {
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
return os.File.openRead(&fixed_allocator.allocator, proc_file_path);
},
Os.macosx, Os.ios => {
Os.macosx,
Os.ios => {
var fixed_buffer_mem: [darwin.PATH_MAX * 2]u8 = undefined;
var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
const self_exe_path = try selfExePath(&fixed_allocator.allocator);
@ -1780,7 +1857,9 @@ pub fn openSelfExe() !os.File {
test "openSelfExe" {
switch (builtin.os) {
Os.linux, Os.macosx, Os.ios => (try openSelfExe()).close(),
Os.linux,
Os.macosx,
Os.ios => (try openSelfExe()).close(),
else => return, // Unsupported OS.
}
}
@ -1818,7 +1897,8 @@ pub fn selfExePath(allocator: &mem.Allocator) ![]u8 {
try out_path.resize(new_len);
}
},
Os.macosx, Os.ios => {
Os.macosx,
Os.ios => {
var u32_len: u32 = 0;
const ret1 = c._NSGetExecutablePath(undefined, &u32_len);
assert(ret1 != 0);
@ -1846,7 +1926,9 @@ pub fn selfExeDirPath(allocator: &mem.Allocator) ![]u8 {
const dir = path.dirname(full_exe_path);
return allocator.shrink(u8, full_exe_path, dir.len);
},
Os.windows, Os.macosx, Os.ios => {
Os.windows,
Os.macosx,
Os.ios => {
const self_exe_path = try selfExePath(allocator);
errdefer allocator.free(self_exe_path);
const dirname = os.path.dirname(self_exe_path);
@ -1903,7 +1985,8 @@ pub fn posixSocket(domain: u32, socket_type: u32, protocol: u32) !i32 {
posix.EINVAL => return PosixSocketError.ProtocolFamilyNotAvailable,
posix.EMFILE => return PosixSocketError.ProcessFdQuotaExceeded,
posix.ENFILE => return PosixSocketError.SystemFdQuotaExceeded,
posix.ENOBUFS, posix.ENOMEM => return PosixSocketError.SystemResources,
posix.ENOBUFS,
posix.ENOMEM => return PosixSocketError.SystemResources,
posix.EPROTONOSUPPORT => return PosixSocketError.ProtocolNotSupported,
else => return unexpectedErrorPosix(err),
}
@ -2072,7 +2155,8 @@ pub fn posixAccept(fd: i32, addr: &posix.sockaddr, flags: u32) PosixAcceptError!
posix.EINVAL => return PosixAcceptError.InvalidSyscall,
posix.EMFILE => return PosixAcceptError.ProcessFdQuotaExceeded,
posix.ENFILE => return PosixAcceptError.SystemFdQuotaExceeded,
posix.ENOBUFS, posix.ENOMEM => return PosixAcceptError.SystemResources,
posix.ENOBUFS,
posix.ENOMEM => return PosixAcceptError.SystemResources,
posix.ENOTSOCK => return PosixAcceptError.FileDescriptorNotASocket,
posix.EOPNOTSUPP => return PosixAcceptError.OperationNotSupported,
posix.EPROTO => return PosixAcceptError.ProtocolFailure,
@ -2283,7 +2367,8 @@ pub fn posixConnectAsync(sockfd: i32, sockaddr: &const posix.sockaddr) PosixConn
const rc = posix.connect(sockfd, sockaddr, @sizeOf(posix.sockaddr));
const err = posix.getErrno(rc);
switch (err) {
0, posix.EINPROGRESS => return,
0,
posix.EINPROGRESS => return,
else => return unexpectedErrorPosix(err),
posix.EACCES => return PosixConnectError.PermissionDenied,
@ -2343,15 +2428,44 @@ pub fn posixGetSockOptConnectError(sockfd: i32) PosixConnectError!void {
}
pub const Thread = struct {
data: Data,
pub const use_pthreads = is_posix and builtin.link_libc;
const Data = if (use_pthreads) struct {
handle: c.pthread_t,
stack_addr: usize,
stack_len: usize,
} else switch (builtin.os) {
builtin.Os.linux => struct {
pid: i32,
allocator: ?&mem.Allocator,
stack: []u8,
stack_addr: usize,
stack_len: usize,
},
builtin.Os.windows => struct {
handle: windows.HANDLE,
alloc_start: &c_void,
heap_handle: windows.HANDLE,
},
else => @compileError("Unsupported OS"),
};
pub fn wait(self: &const Thread) void {
if (use_pthreads) {
const err = c.pthread_join(self.data.handle, null);
switch (err) {
0 => {},
posix.EINVAL => unreachable,
posix.ESRCH => unreachable,
posix.EDEADLK => unreachable,
else => unreachable,
}
assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
} else switch (builtin.os) {
builtin.Os.linux => {
while (true) {
const pid_value = @atomicLoad(i32, &self.pid, builtin.AtomicOrder.SeqCst);
const pid_value = @atomicLoad(i32, &self.data.pid, builtin.AtomicOrder.SeqCst);
if (pid_value == 0) break;
const rc = linux.futex_wait(@ptrToInt(&self.pid), linux.FUTEX_WAIT, pid_value, null);
const rc = linux.futex_wait(@ptrToInt(&self.data.pid), linux.FUTEX_WAIT, pid_value, null);
switch (linux.getErrno(rc)) {
0 => continue,
posix.EINTR => continue,
@ -2359,8 +2473,13 @@ pub const Thread = struct {
else => unreachable,
}
}
if (self.allocator) |a| {
a.free(self.stack);
assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
},
builtin.Os.windows => {
assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
assert(windows.HeapFree(self.data.heap_handle, 0, self.data.alloc_start) != 0);
},
else => @compileError("Unsupported OS"),
}
}
};
@ -2385,38 +2504,91 @@ pub const SpawnThreadError = error {
/// be copied.
SystemResources,
/// Not enough userland memory to spawn the thread.
OutOfMemory,
Unexpected,
};
pub const SpawnThreadAllocatorError = SpawnThreadError || error{OutOfMemory};
/// caller must call wait on the returned thread
/// fn startFn(@typeOf(context)) T
/// where T is u8, noreturn, void, or !void
pub fn spawnThreadAllocator(allocator: &mem.Allocator, context: var, comptime startFn: var) SpawnThreadAllocatorError!&Thread {
/// caller must call wait on the returned thread
pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!&Thread {
// TODO compile-time call graph analysis to determine stack upper bound
// https://github.com/zig-lang/zig/issues/157
const default_stack_size = 8 * 1024 * 1024;
const stack_bytes = try allocator.alloc(u8, default_stack_size);
const thread = try spawnThread(stack_bytes, context, startFn);
thread.allocator = allocator;
return thread;
}
/// stack must be big enough to store one Thread and one @typeOf(context), each with default alignment, at the end
/// fn startFn(@typeOf(context)) T
/// where T is u8, noreturn, void, or !void
/// caller must call wait on the returned thread
pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThreadError!&Thread {
const Context = @typeOf(context);
comptime assert(@ArgType(@typeOf(startFn), 0) == Context);
var stack_end: usize = @ptrToInt(stack.ptr) + stack.len;
if (builtin.os == builtin.Os.windows) {
const WinThread = struct {
const OuterContext = struct {
thread: Thread,
inner: Context,
};
extern fn threadMain(arg: windows.LPVOID) windows.DWORD {
if (@sizeOf(Context) == 0) {
return startFn({});
} else {
return startFn(*@ptrCast(&Context, @alignCast(@alignOf(Context), arg)));
}
}
};
const heap_handle = windows.GetProcessHeap() ?? return SpawnThreadError.OutOfMemory;
const byte_count = @alignOf(WinThread.OuterContext) + @sizeOf(WinThread.OuterContext);
const bytes_ptr = windows.HeapAlloc(heap_handle, 0, byte_count) ?? return SpawnThreadError.OutOfMemory;
errdefer assert(windows.HeapFree(heap_handle, 0, bytes_ptr) != 0);
const bytes = @ptrCast(&u8, bytes_ptr)[0..byte_count];
const outer_context = std.heap.FixedBufferAllocator.init(bytes).allocator.create(WinThread.OuterContext) catch unreachable;
outer_context.inner = context;
outer_context.thread.data.heap_handle = heap_handle;
outer_context.thread.data.alloc_start = bytes_ptr;
const parameter = if (@sizeOf(Context) == 0) null else @ptrCast(&c_void, &outer_context.inner);
outer_context.thread.data.handle = windows.CreateThread(null, default_stack_size, WinThread.threadMain, parameter, 0, null) ?? {
const err = windows.GetLastError();
return switch (err) {
else => os.unexpectedErrorWindows(err),
};
};
return &outer_context.thread;
}
const MainFuncs = struct {
extern fn linuxThreadMain(ctx_addr: usize) u8 {
if (@sizeOf(Context) == 0) {
return startFn({});
} else {
return startFn(*@intToPtr(&const Context, ctx_addr));
}
}
extern fn posixThreadMain(ctx: ?&c_void) ?&c_void {
if (@sizeOf(Context) == 0) {
_ = startFn({});
return null;
} else {
_ = startFn(*@ptrCast(&const Context, @alignCast(@alignOf(Context), ctx)));
return null;
}
}
};
const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0;
const mmap_len = default_stack_size;
const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory;
errdefer assert(posix.munmap(stack_addr, mmap_len) == 0);
var stack_end: usize = stack_addr + mmap_len;
var arg: usize = undefined;
if (@sizeOf(Context) != 0) {
stack_end -= @sizeOf(Context);
stack_end -= stack_end % @alignOf(Context);
assert(stack_end >= @ptrToInt(stack.ptr));
assert(stack_end >= stack_addr);
const context_ptr = @alignCast(@alignOf(Context), @intToPtr(&Context, stack_end));
*context_ptr = context;
arg = stack_end;
@ -2424,26 +2596,38 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread
stack_end -= @sizeOf(Thread);
stack_end -= stack_end % @alignOf(Thread);
assert(stack_end >= @ptrToInt(stack.ptr));
assert(stack_end >= stack_addr);
const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(&Thread, stack_end));
thread_ptr.stack = stack;
thread_ptr.allocator = null;
const threadMain = struct {
extern fn threadMain(ctx_addr: usize) u8 {
if (@sizeOf(Context) == 0) {
return startFn({});
} else {
return startFn(*@intToPtr(&const Context, ctx_addr));
}
}
}.threadMain;
thread_ptr.data.stack_addr = stack_addr;
thread_ptr.data.stack_len = mmap_len;
const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND
| posix.CLONE_THREAD | posix.CLONE_SYSVSEM // | posix.CLONE_SETTLS
| posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED;
if (builtin.os == builtin.Os.windows) {
// use windows API directly
@compileError("TODO support spawnThread for Windows");
} else if (Thread.use_pthreads) {
// use pthreads
var attr: c.pthread_attr_t = undefined;
if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources;
defer assert(c.pthread_attr_destroy(&attr) == 0);
// align to page
stack_end -= stack_end % os.page_size;
assert(c.pthread_attr_setstack(&attr, @intToPtr(&c_void, stack_addr), stack_end - stack_addr) == 0);
const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(&c_void, arg));
switch (err) {
0 => return thread_ptr,
posix.EAGAIN => return SpawnThreadError.SystemResources,
posix.EPERM => unreachable,
posix.EINVAL => unreachable,
else => return unexpectedErrorPosix(usize(err)),
}
} else if (builtin.os == builtin.Os.linux) {
// use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly
const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED;
const newtls: usize = 0;
const rc = posix.clone(threadMain, stack_end, flags, arg, &thread_ptr.pid, newtls, &thread_ptr.pid);
const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.pid, newtls, &thread_ptr.data.pid);
const err = posix.getErrno(rc);
switch (err) {
0 => return thread_ptr,
@ -2455,6 +2639,9 @@ pub fn spawnThread(stack: []u8, context: var, comptime startFn: var) SpawnThread
posix.EUSERS => unreachable,
else => return unexpectedErrorPosix(err),
}
} else {
@compileError("Unsupported OS");
}
}
pub fn posixWait(pid: i32) i32 {

View File

@ -706,13 +706,13 @@ pub fn umount2(special: &const u8, flags: u32) usize {
return syscall2(SYS_umount2, @ptrToInt(special), flags);
}
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: isize) usize {
pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: u32, fd: i32, offset: isize) usize {
return syscall6(SYS_mmap, @ptrToInt(address), length, prot, flags, usize(fd),
@bitCast(usize, offset));
}
pub fn munmap(address: &u8, length: usize) usize {
return syscall2(SYS_munmap, @ptrToInt(address), length);
pub fn munmap(address: usize, length: usize) usize {
return syscall2(SYS_munmap, address, length);
}
pub fn read(fd: i32, buf: &u8, count: usize) usize {

View File

@ -44,24 +44,12 @@ test "access file" {
}
test "spawn threads" {
if (builtin.os != builtin.Os.linux) {
// TODO implement threads on macos and windows
return;
}
var direct_allocator = std.heap.DirectAllocator.init();
defer direct_allocator.deinit();
var shared_ctx: i32 = 1;
const thread1 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, {}, start1);
const thread4 = try std.os.spawnThreadAllocator(&direct_allocator.allocator, &shared_ctx, start2);
var stack1: [1024]u8 = undefined;
var stack2: [1024]u8 = undefined;
const thread2 = try std.os.spawnThread(stack1[0..], &shared_ctx, start2);
const thread3 = try std.os.spawnThread(stack2[0..], &shared_ctx, start2);
const thread1 = try std.os.spawnThread({}, start1);
const thread2 = try std.os.spawnThread(&shared_ctx, start2);
const thread3 = try std.os.spawnThread(&shared_ctx, start2);
const thread4 = try std.os.spawnThread(&shared_ctx, start2);
thread1.wait();
thread2.wait();

View File

@ -281,7 +281,7 @@ test "os.time.Timer" {
debug.assert(time_0 > 0 and time_0 < margin);
const time_1 = timer.lap();
debug.assert(time_1 > time_0);
debug.assert(time_1 >= time_0);
timer.reset();
debug.assert(timer.read() < time_1);

View File

@ -28,6 +28,9 @@ pub extern "kernel32" stdcallcc fn CreateProcessA(lpApplicationName: ?LPCSTR, lp
pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(lpSymlinkFileName: LPCSTR, lpTargetFileName: LPCSTR,
dwFlags: DWORD) BOOLEAN;
pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
@ -318,6 +321,9 @@ pub const HEAP_CREATE_ENABLE_EXECUTE = 0x00040000;
pub const HEAP_GENERATE_EXCEPTIONS = 0x00000004;
pub const HEAP_NO_SERIALIZE = 0x00000001;
pub const PTHREAD_START_ROUTINE = extern fn(LPVOID) DWORD;
pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE;
test "import" {
_ = @import("util.zig");
}

View File

@ -1,9 +1,10 @@
// This file is included in the compilation unit when exporting a library on windows.
const std = @import("std");
const builtin = @import("builtin");
comptime {
@export("_DllMainCRTStartup", _DllMainCRTStartup);
@export("_DllMainCRTStartup", _DllMainCRTStartup, builtin.GlobalLinkage.Strong);
}
stdcallcc fn _DllMainCRTStartup(hinstDLL: std.os.windows.HINSTANCE, fdwReason: std.os.windows.DWORD,

View File

@ -32,10 +32,6 @@ comptime {
@export("__fixunstfti", @import("fixunstfti.zig").__fixunstfti, linkage);
@export("__udivmoddi4", @import("udivmoddi4.zig").__udivmoddi4, linkage);
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage);
@export("__udivti3", @import("udivti3.zig").__udivti3, linkage);
@export("__umodti3", @import("umodti3.zig").__umodti3, linkage);
@export("__udivsi3", __udivsi3, linkage);
@export("__udivdi3", __udivdi3, linkage);
@ -62,9 +58,16 @@ comptime {
@export("__chkstk", __chkstk, strong_linkage);
@export("___chkstk_ms", ___chkstk_ms, linkage);
}
@export("__udivti3", @import("udivti3.zig").__udivti3_windows_x86_64, linkage);
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4_windows_x86_64, linkage);
@export("__umodti3", @import("umodti3.zig").__umodti3_windows_x86_64, linkage);
},
else => {},
}
} else {
@export("__udivti3", @import("udivti3.zig").__udivti3, linkage);
@export("__udivmodti4", @import("udivmodti4.zig").__udivmodti4, linkage);
@export("__umodti3", @import("umodti3.zig").__umodti3, linkage);
}
}
@ -83,6 +86,16 @@ pub fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) noreturn
}
}
pub fn setXmm0(comptime T: type, value: T) void {
comptime assert(builtin.arch == builtin.Arch.x86_64);
const aligned_value: T align(16) = value;
asm volatile (
\\movaps (%[ptr]), %%xmm0
:
: [ptr] "r" (&aligned_value)
: "xmm0");
}
extern fn __udivdi3(a: u64, b: u64) u64 {
@setRuntimeSafety(is_test);
return __udivmoddi4(a, b, null);

View File

@ -1,11 +1,17 @@
const udivmod = @import("udivmod.zig").udivmod;
const builtin = @import("builtin");
const compiler_rt = @import("index.zig");
pub extern fn __udivmodti4(a: u128, b: u128, maybe_rem: ?&u128) u128 {
@setRuntimeSafety(builtin.is_test);
return udivmod(u128, a, b, maybe_rem);
}
pub extern fn __udivmodti4_windows_x86_64(a: &const u128, b: &const u128, maybe_rem: ?&u128) void {
@setRuntimeSafety(builtin.is_test);
compiler_rt.setXmm0(u128, udivmod(u128, *a, *b, maybe_rem));
}
test "import udivmodti4" {
_ = @import("udivmodti4_test.zig");
}

View File

@ -1,7 +1,12 @@
const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4;
const udivmodti4 = @import("udivmodti4.zig");
const builtin = @import("builtin");
pub extern fn __udivti3(a: u128, b: u128) u128 {
@setRuntimeSafety(builtin.is_test);
return __udivmodti4(a, b, null);
return udivmodti4.__udivmodti4(a, b, null);
}
pub extern fn __udivti3_windows_x86_64(a: &const u128, b: &const u128) void {
@setRuntimeSafety(builtin.is_test);
udivmodti4.__udivmodti4_windows_x86_64(a, b, null);
}

View File

@ -1,9 +1,15 @@
const __udivmodti4 = @import("udivmodti4.zig").__udivmodti4;
const udivmodti4 = @import("udivmodti4.zig");
const builtin = @import("builtin");
const compiler_rt = @import("index.zig");
pub extern fn __umodti3(a: u128, b: u128) u128 {
@setRuntimeSafety(builtin.is_test);
var r: u128 = undefined;
_ = __udivmodti4(a, b, &r);
_ = udivmodti4.__udivmodti4(a, b, &r);
return r;
}
pub extern fn __umodti3_windows_x86_64(a: &const u128, b: &const u128) void {
@setRuntimeSafety(builtin.is_test);
compiler_rt.setXmm0(u128, __umodti3(*a, *b));
}

View File

@ -1,6 +1,16 @@
const std = @import("./index.zig");
const debug = std.debug;
/// Returns how many bytes the UTF-8 representation would require
/// for the given codepoint.
pub fn utf8CodepointSequenceLength(c: u32) !u3 {
if (c < 0x80) return u3(1);
if (c < 0x800) return u3(2);
if (c < 0x10000) return u3(3);
if (c < 0x110000) return u3(4);
return error.CodepointTooLarge;
}
/// Given the first byte of a UTF-8 codepoint,
/// returns a number 1-4 indicating the total length of the codepoint in bytes.
/// If this byte does not match the form of a UTF-8 start byte, returns Utf8InvalidStartByte.
@ -12,11 +22,47 @@ pub fn utf8ByteSequenceLength(first_byte: u8) !u3 {
return error.Utf8InvalidStartByte;
}
/// Encodes the given codepoint into a UTF-8 byte sequence.
/// c: the codepoint.
/// out: the out buffer to write to. Must have a len >= utf8CodepointSequenceLength(c).
/// Errors: if c cannot be encoded in UTF-8.
/// Returns: the number of bytes written to out.
pub fn utf8Encode(c: u32, out: []u8) !u3 {
const length = try utf8CodepointSequenceLength(c);
debug.assert(out.len >= length);
switch (length) {
// The pattern for each is the same
// - Increasing the initial shift by 6 each time
// - Each time after the first shorten the shifted
// value to a max of 0b111111 (63)
1 => out[0] = u8(c), // Can just do 0 + codepoint for initial range
2 => {
out[0] = u8(0b11000000 | (c >> 6));
out[1] = u8(0b10000000 | (c & 0b111111));
},
3 => {
if (0xd800 <= c and c <= 0xdfff) return error.Utf8CannotEncodeSurrogateHalf;
out[0] = u8(0b11100000 | (c >> 12));
out[1] = u8(0b10000000 | ((c >> 6) & 0b111111));
out[2] = u8(0b10000000 | (c & 0b111111));
},
4 => {
out[0] = u8(0b11110000 | (c >> 18));
out[1] = u8(0b10000000 | ((c >> 12) & 0b111111));
out[2] = u8(0b10000000 | ((c >> 6) & 0b111111));
out[3] = u8(0b10000000 | (c & 0b111111));
},
else => unreachable,
}
return length;
}
const Utf8DecodeError = Utf8Decode2Error || Utf8Decode3Error || Utf8Decode4Error;
/// Decodes the UTF-8 codepoint encoded in the given slice of bytes.
/// bytes.len must be equal to utf8ByteSequenceLength(bytes[0]) catch unreachable.
/// If you already know the length at comptime, you can call one of
/// utf8Decode2,utf8Decode3,utf8Decode4 directly instead of this function.
pub fn utf8Decode(bytes: []const u8) !u32 {
pub fn utf8Decode(bytes: []const u8) Utf8DecodeError!u32 {
return switch (bytes.len) {
1 => u32(bytes[0]),
2 => utf8Decode2(bytes),
@ -25,7 +71,12 @@ pub fn utf8Decode(bytes: []const u8) !u32 {
else => unreachable,
};
}
pub fn utf8Decode2(bytes: []const u8) !u32 {
const Utf8Decode2Error = error{
Utf8ExpectedContinuation,
Utf8OverlongEncoding,
};
pub fn utf8Decode2(bytes: []const u8) Utf8Decode2Error!u32 {
debug.assert(bytes.len == 2);
debug.assert(bytes[0] & 0b11100000 == 0b11000000);
var value: u32 = bytes[0] & 0b00011111;
@ -38,7 +89,13 @@ pub fn utf8Decode2(bytes: []const u8) !u32 {
return value;
}
pub fn utf8Decode3(bytes: []const u8) !u32 {
const Utf8Decode3Error = error{
Utf8ExpectedContinuation,
Utf8OverlongEncoding,
Utf8EncodesSurrogateHalf,
};
pub fn utf8Decode3(bytes: []const u8) Utf8Decode3Error!u32 {
debug.assert(bytes.len == 3);
debug.assert(bytes[0] & 0b11110000 == 0b11100000);
var value: u32 = bytes[0] & 0b00001111;
@ -56,7 +113,13 @@ pub fn utf8Decode3(bytes: []const u8) !u32 {
return value;
}
pub fn utf8Decode4(bytes: []const u8) !u32 {
const Utf8Decode4Error = error{
Utf8ExpectedContinuation,
Utf8OverlongEncoding,
Utf8CodepointTooLarge,
};
pub fn utf8Decode4(bytes: []const u8) Utf8Decode4Error!u32 {
debug.assert(bytes.len == 4);
debug.assert(bytes[0] & 0b11111000 == 0b11110000);
var value: u32 = bytes[0] & 0b00000111;
@ -158,19 +221,67 @@ const Utf8Iterator = struct {
pub fn nextCodepoint(it: &Utf8Iterator) ?u32 {
const slice = it.nextCodepointSlice() ?? return null;
const r = switch (slice.len) {
1 => u32(slice[0]),
2 => utf8Decode2(slice),
3 => utf8Decode3(slice),
4 => utf8Decode4(slice),
switch (slice.len) {
1 => return u32(slice[0]),
2 => return utf8Decode2(slice) catch unreachable,
3 => return utf8Decode3(slice) catch unreachable,
4 => return utf8Decode4(slice) catch unreachable,
else => unreachable,
};
return r catch unreachable;
}
}
};
test "utf8 encode" {
comptime testUtf8Encode() catch unreachable;
try testUtf8Encode();
}
fn testUtf8Encode() !void {
// A few taken from wikipedia a few taken elsewhere
var array: [4]u8 = undefined;
debug.assert((try utf8Encode(try utf8Decode(""), array[0..])) == 3);
debug.assert(array[0] == 0b11100010);
debug.assert(array[1] == 0b10000010);
debug.assert(array[2] == 0b10101100);
debug.assert((try utf8Encode(try utf8Decode("$"), array[0..])) == 1);
debug.assert(array[0] == 0b00100100);
debug.assert((try utf8Encode(try utf8Decode("¢"), array[0..])) == 2);
debug.assert(array[0] == 0b11000010);
debug.assert(array[1] == 0b10100010);
debug.assert((try utf8Encode(try utf8Decode("𐍈"), array[0..])) == 4);
debug.assert(array[0] == 0b11110000);
debug.assert(array[1] == 0b10010000);
debug.assert(array[2] == 0b10001101);
debug.assert(array[3] == 0b10001000);
}
test "utf8 encode error" {
comptime testUtf8EncodeError();
testUtf8EncodeError();
}
fn testUtf8EncodeError() void {
var array: [4]u8 = undefined;
testErrorEncode(0xd800, array[0..], error.Utf8CannotEncodeSurrogateHalf);
testErrorEncode(0xdfff, array[0..], error.Utf8CannotEncodeSurrogateHalf);
testErrorEncode(0x110000, array[0..], error.CodepointTooLarge);
testErrorEncode(0xffffffff, array[0..], error.CodepointTooLarge);
}
fn testErrorEncode(codePoint: u32, array: []u8, expectedErr: error) void {
if (utf8Encode(codePoint, array)) |_| {
unreachable;
} else |err| {
debug.assert(err == expectedErr);
}
}
test "utf8 iterator on ascii" {
comptime testUtf8IteratorOnAscii();
testUtf8IteratorOnAscii();
}
fn testUtf8IteratorOnAscii() void {
const s = Utf8View.initComptime("abc");
var it1 = s.iterator();
@ -187,6 +298,10 @@ test "utf8 iterator on ascii" {
}
test "utf8 view bad" {
comptime testUtf8ViewBad();
testUtf8ViewBad();
}
fn testUtf8ViewBad() void {
// Compile-time error.
// const s3 = Utf8View.initComptime("\xfe\xf2");
@ -195,6 +310,10 @@ test "utf8 view bad" {
}
test "utf8 view ok" {
comptime testUtf8ViewOk();
testUtf8ViewOk();
}
fn testUtf8ViewOk() void {
const s = Utf8View.initComptime("東京市");
var it1 = s.iterator();
@ -211,6 +330,10 @@ test "utf8 view ok" {
}
test "bad utf8 slice" {
comptime testBadUtf8Slice();
testBadUtf8Slice();
}
fn testBadUtf8Slice() void {
debug.assert(utf8ValidateSlice("abc"));
debug.assert(!utf8ValidateSlice("abc\xc0"));
debug.assert(!utf8ValidateSlice("abc\xc0abc"));
@ -218,6 +341,10 @@ test "bad utf8 slice" {
}
test "valid utf8" {
comptime testValidUtf8();
testValidUtf8();
}
fn testValidUtf8() void {
testValid("\x00", 0x0);
testValid("\x20", 0x20);
testValid("\x7f", 0x7f);
@ -233,6 +360,10 @@ test "valid utf8" {
}
test "invalid utf8 continuation bytes" {
comptime testInvalidUtf8ContinuationBytes();
testInvalidUtf8ContinuationBytes();
}
fn testInvalidUtf8ContinuationBytes() void {
// unexpected continuation
testError("\x80", error.Utf8InvalidStartByte);
testError("\xbf", error.Utf8InvalidStartByte);
@ -261,6 +392,10 @@ test "invalid utf8 continuation bytes" {
}
test "overlong utf8 codepoint" {
comptime testOverlongUtf8Codepoint();
testOverlongUtf8Codepoint();
}
fn testOverlongUtf8Codepoint() void {
testError("\xc0\x80", error.Utf8OverlongEncoding);
testError("\xc1\xbf", error.Utf8OverlongEncoding);
testError("\xe0\x80\x80", error.Utf8OverlongEncoding);
@ -270,6 +405,10 @@ test "overlong utf8 codepoint" {
}
test "misc invalid utf8" {
comptime testMiscInvalidUtf8();
testMiscInvalidUtf8();
}
fn testMiscInvalidUtf8() void {
// codepoint out of bounds
testError("\xf4\x90\x80\x80", error.Utf8CodepointTooLarge);
testError("\xf7\xbf\xbf\xbf", error.Utf8CodepointTooLarge);

View File

@ -6,6 +6,7 @@ const mem = std.mem;
pub const Node = struct {
id: Id,
same_line_comment: ?&Token,
pub const Id = enum {
// Top level
@ -34,6 +35,7 @@ pub const Node = struct {
VarType,
ErrorType,
FnProto,
PromiseType,
// Primary expressions
IntegerLiteral,
@ -57,6 +59,7 @@ pub const Node = struct {
// Misc
LineComment,
DocComment,
SwitchCase,
SwitchElse,
Else,
@ -66,6 +69,7 @@ pub const Node = struct {
StructField,
UnionTag,
EnumTag,
ErrorTag,
AsmInput,
AsmOutput,
AsyncAttribute,
@ -73,6 +77,13 @@ pub const Node = struct {
FieldInitializer,
};
pub fn cast(base: &Node, comptime T: type) ?&T {
if (base.id == comptime typeToId(T)) {
return @fieldParentPtr(T, "base", base);
}
return null;
}
pub fn iterate(base: &Node, index: usize) ?&Node {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
@ -118,6 +129,7 @@ pub const Node = struct {
pub const Root = struct {
base: Node,
doc_comments: ?&DocComment,
decls: ArrayList(&Node),
eof_token: Token,
@ -139,7 +151,7 @@ pub const Node = struct {
pub const VarDecl = struct {
base: Node,
comments: ?&LineComment,
doc_comments: ?&DocComment,
visib_token: ?Token,
name_token: Token,
eq_token: Token,
@ -188,6 +200,7 @@ pub const Node = struct {
pub const Use = struct {
base: Node,
doc_comments: ?&DocComment,
visib_token: ?Token,
expr: &Node,
semicolon_token: Token,
@ -258,7 +271,7 @@ pub const Node = struct {
const InitArg = union(enum) {
None,
Enum,
Enum: ?&Node,
Type: &Node,
};
@ -291,6 +304,7 @@ pub const Node = struct {
pub const StructField = struct {
base: Node,
doc_comments: ?&DocComment,
visib_token: ?Token,
name_token: Token,
type_expr: &Node,
@ -316,8 +330,10 @@ pub const Node = struct {
pub const UnionTag = struct {
base: Node,
doc_comments: ?&DocComment,
name_token: Token,
type_expr: ?&Node,
value_expr: ?&Node,
pub fn iterate(self: &UnionTag, index: usize) ?&Node {
var i = index;
@ -327,6 +343,11 @@ pub const Node = struct {
i -= 1;
}
if (self.value_expr) |value_expr| {
if (i < 1) return value_expr;
i -= 1;
}
return null;
}
@ -335,6 +356,9 @@ pub const Node = struct {
}
pub fn lastToken(self: &UnionTag) Token {
if (self.value_expr) |value_expr| {
return value_expr.lastToken();
}
if (self.type_expr) |type_expr| {
return type_expr.lastToken();
}
@ -345,6 +369,7 @@ pub const Node = struct {
pub const EnumTag = struct {
base: Node,
doc_comments: ?&DocComment,
name_token: Token,
value: ?&Node,
@ -372,6 +397,31 @@ pub const Node = struct {
}
};
pub const ErrorTag = struct {
base: Node,
doc_comments: ?&DocComment,
name_token: Token,
pub fn iterate(self: &ErrorTag, index: usize) ?&Node {
var i = index;
if (self.doc_comments) |comments| {
if (i < 1) return &comments.base;
i -= 1;
}
return null;
}
pub fn firstToken(self: &ErrorTag) Token {
return self.name_token;
}
pub fn lastToken(self: &ErrorTag) Token {
return self.name_token;
}
};
pub const Identifier = struct {
base: Node,
token: Token,
@ -421,7 +471,7 @@ pub const Node = struct {
pub const FnProto = struct {
base: Node,
comments: ?&LineComment,
doc_comments: ?&DocComment,
visib_token: ?Token,
fn_token: Token,
name_token: ?Token,
@ -494,6 +544,37 @@ pub const Node = struct {
}
};
pub const PromiseType = struct {
base: Node,
promise_token: Token,
result: ?Result,
pub const Result = struct {
arrow_token: Token,
return_type: &Node,
};
pub fn iterate(self: &PromiseType, index: usize) ?&Node {
var i = index;
if (self.result) |result| {
if (i < 1) return result.return_type;
i -= 1;
}
return null;
}
pub fn firstToken(self: &PromiseType) Token {
return self.promise_token;
}
pub fn lastToken(self: &PromiseType) Token {
if (self.result) |result| return result.return_type.lastToken();
return self.promise_token;
}
};
pub const ParamDecl = struct {
base: Node,
comptime_token: ?Token,
@ -584,6 +665,7 @@ pub const Node = struct {
pub const Comptime = struct {
base: Node,
doc_comments: ?&DocComment,
comptime_token: Token,
expr: &Node,
@ -718,7 +800,8 @@ pub const Node = struct {
base: Node,
switch_token: Token,
expr: &Node,
cases: ArrayList(&SwitchCase),
/// these can be SwitchCase nodes or LineComment nodes
cases: ArrayList(&Node),
rbrace: Token,
pub fn iterate(self: &Switch, index: usize) ?&Node {
@ -727,7 +810,7 @@ pub const Node = struct {
if (i < 1) return self.expr;
i -= 1;
if (i < self.cases.len) return &self.cases.at(i).base;
if (i < self.cases.len) return self.cases.at(i);
i -= self.cases.len;
return null;
@ -1186,7 +1269,7 @@ pub const Node = struct {
ArrayAccess: &Node,
Slice: SliceRange,
ArrayInitializer: ArrayList(&Node),
StructInitializer: ArrayList(&FieldInitializer),
StructInitializer: ArrayList(&Node),
};
const CallInfo = struct {
@ -1228,7 +1311,7 @@ pub const Node = struct {
i -= exprs.len;
},
Op.StructInitializer => |fields| {
if (i < fields.len) return &fields.at(i).base;
if (i < fields.len) return fields.at(i);
i -= fields.len;
},
}
@ -1337,6 +1420,7 @@ pub const Node = struct {
pub const Suspend = struct {
base: Node,
label: ?Token,
suspend_token: Token,
payload: ?&Node,
body: ?&Node,
@ -1358,6 +1442,7 @@ pub const Node = struct {
}
pub fn firstToken(self: &Suspend) Token {
if (self.label) |label| return label;
return self.suspend_token;
}
@ -1715,24 +1800,41 @@ pub const Node = struct {
pub const LineComment = struct {
base: Node,
lines: ArrayList(Token),
token: Token,
pub fn iterate(self: &LineComment, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &LineComment) Token {
return self.lines.at(0);
return self.token;
}
pub fn lastToken(self: &LineComment) Token {
return self.token;
}
};
pub const DocComment = struct {
base: Node,
lines: ArrayList(Token),
pub fn iterate(self: &DocComment, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &DocComment) Token {
return self.lines.at(0);
}
pub fn lastToken(self: &DocComment) Token {
return self.lines.at(self.lines.len - 1);
}
};
pub const TestDecl = struct {
base: Node,
comments: ?&LineComment,
doc_comments: ?&DocComment,
test_token: Token,
name: &Node,
body_node: &Node,

File diff suppressed because it is too large Load Diff

1139
std/zig/parser_test.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -40,6 +40,7 @@ pub const Token = struct {
KeywordId{.bytes="null", .id = Id.Keyword_null},
KeywordId{.bytes="or", .id = Id.Keyword_or},
KeywordId{.bytes="packed", .id = Id.Keyword_packed},
KeywordId{.bytes="promise", .id = Id.Keyword_promise},
KeywordId{.bytes="pub", .id = Id.Keyword_pub},
KeywordId{.bytes="resume", .id = Id.Keyword_resume},
KeywordId{.bytes="return", .id = Id.Keyword_return},
@ -137,6 +138,7 @@ pub const Token = struct {
IntegerLiteral,
FloatLiteral,
LineComment,
DocComment,
Keyword_align,
Keyword_and,
Keyword_asm,
@ -165,6 +167,7 @@ pub const Token = struct {
Keyword_null,
Keyword_or,
Keyword_packed,
Keyword_promise,
Keyword_pub,
Keyword_resume,
Keyword_return,
@ -257,7 +260,10 @@ pub const Tokenizer = struct {
Asterisk,
AsteriskPercent,
Slash,
LineCommentStart,
LineComment,
DocCommentStart,
DocComment,
Zero,
IntegerLiteral,
IntegerLiteralWithRadix,
@ -822,8 +828,8 @@ pub const Tokenizer = struct {
State.Slash => switch (c) {
'/' => {
state = State.LineCommentStart;
result.id = Token.Id.LineComment;
state = State.LineComment;
},
'=' => {
result.id = Token.Id.SlashEqual;
@ -835,7 +841,31 @@ pub const Tokenizer = struct {
break;
},
},
State.LineComment => switch (c) {
State.LineCommentStart => switch (c) {
'/' => {
state = State.DocCommentStart;
},
'\n' => break,
else => {
state = State.LineComment;
self.checkLiteralCharacter();
},
},
State.DocCommentStart => switch (c) {
'/' => {
state = State.LineComment;
},
'\n' => {
result.id = Token.Id.DocComment;
break;
},
else => {
state = State.DocComment;
result.id = Token.Id.DocComment;
self.checkLiteralCharacter();
},
},
State.LineComment, State.DocComment => switch (c) {
'\n' => break,
else => self.checkLiteralCharacter(),
},
@ -920,8 +950,12 @@ pub const Tokenizer = struct {
result.id = id;
}
},
State.LineCommentStart,
State.LineComment => {
result.id = Token.Id.Eof;
result.id = Token.Id.LineComment;
},
State.DocComment, State.DocCommentStart => {
result.id = Token.Id.DocComment;
},
State.NumberDot,
@ -1092,41 +1126,77 @@ test "tokenizer - invalid literal/comment characters" {
Token.Id.Invalid,
});
testTokenize("//\x00", []Token.Id {
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\x1f", []Token.Id {
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\x7f", []Token.Id {
Token.Id.LineComment,
Token.Id.Invalid,
});
}
test "tokenizer - utf8" {
testTokenize("//\xc2\x80", []Token.Id{});
testTokenize("//\xf4\x8f\xbf\xbf", []Token.Id{});
testTokenize("//\xc2\x80", []Token.Id{Token.Id.LineComment});
testTokenize("//\xf4\x8f\xbf\xbf", []Token.Id{Token.Id.LineComment});
}
test "tokenizer - invalid utf8" {
testTokenize("//\x80", []Token.Id{Token.Id.Invalid});
testTokenize("//\xbf", []Token.Id{Token.Id.Invalid});
testTokenize("//\xf8", []Token.Id{Token.Id.Invalid});
testTokenize("//\xff", []Token.Id{Token.Id.Invalid});
testTokenize("//\xc2\xc0", []Token.Id{Token.Id.Invalid});
testTokenize("//\xe0", []Token.Id{Token.Id.Invalid});
testTokenize("//\xf0", []Token.Id{Token.Id.Invalid});
testTokenize("//\xf0\x90\x80\xc0", []Token.Id{Token.Id.Invalid});
testTokenize("//\x80", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xbf", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xf8", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xff", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xc2\xc0", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xe0", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xf0", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xf0\x90\x80\xc0", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
}
test "tokenizer - illegal unicode codepoints" {
// unicode newline characters.U+0085, U+2028, U+2029
testTokenize("//\xc2\x84", []Token.Id{});
testTokenize("//\xc2\x85", []Token.Id{Token.Id.Invalid});
testTokenize("//\xc2\x86", []Token.Id{});
testTokenize("//\xe2\x80\xa7", []Token.Id{});
testTokenize("//\xe2\x80\xa8", []Token.Id{Token.Id.Invalid});
testTokenize("//\xe2\x80\xa9", []Token.Id{Token.Id.Invalid});
testTokenize("//\xe2\x80\xaa", []Token.Id{});
testTokenize("//\xc2\x84", []Token.Id{Token.Id.LineComment});
testTokenize("//\xc2\x85", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xc2\x86", []Token.Id{Token.Id.LineComment});
testTokenize("//\xe2\x80\xa7", []Token.Id{Token.Id.LineComment});
testTokenize("//\xe2\x80\xa8", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xe2\x80\xa9", []Token.Id{
Token.Id.LineComment,
Token.Id.Invalid,
});
testTokenize("//\xe2\x80\xaa", []Token.Id{Token.Id.LineComment});
}
test "tokenizer - string identifier and builtin fns" {
@ -1153,11 +1223,36 @@ test "tokenizer - pipe and then invalid" {
});
}
test "tokenizer - line comment and doc comment" {
testTokenize("//", []Token.Id{Token.Id.LineComment});
testTokenize("// a / b", []Token.Id{Token.Id.LineComment});
testTokenize("// /", []Token.Id{Token.Id.LineComment});
testTokenize("/// a", []Token.Id{Token.Id.DocComment});
testTokenize("///", []Token.Id{Token.Id.DocComment});
testTokenize("////", []Token.Id{Token.Id.LineComment});
}
test "tokenizer - line comment followed by identifier" {
testTokenize(
\\ Unexpected,
\\ // another
\\ Another,
, []Token.Id{
Token.Id.Identifier,
Token.Id.Comma,
Token.Id.LineComment,
Token.Id.Identifier,
Token.Id.Comma,
});
}
fn testTokenize(source: []const u8, expected_tokens: []const Token.Id) void {
var tokenizer = Tokenizer.init(source);
for (expected_tokens) |expected_token_id| {
const token = tokenizer.next();
std.debug.assert(@TagType(Token.Id)(token.id) == @TagType(Token.Id)(expected_token_id));
if (@TagType(Token.Id)(token.id) != @TagType(Token.Id)(expected_token_id)) {
std.debug.panic("expected {}, found {}\n", @tagName(@TagType(Token.Id)(expected_token_id)), @tagName(@TagType(Token.Id)(token.id)));
}
switch (expected_token_id) {
Token.Id.StringLiteral => |expected_kind| {
std.debug.assert(expected_kind == switch (token.id) { Token.Id.StringLiteral => |kind| kind, else => unreachable });

View File

@ -41,3 +41,14 @@ fn testBreakContInDefer(x: usize) void {
assert(i == 5);
}
}
test "defer and labeled break" {
var i = usize(0);
blk: {
defer i += 1;
break :blk;
}
assert(i == 1);
}

View File

@ -175,3 +175,69 @@ fn baz_1() !i32 {
fn quux_1() !i32 {
return error.C;
}
test "error: fn returning empty error set can be passed as fn returning any error" {
entry();
comptime entry();
}
fn entry() void {
foo2(bar2);
}
fn foo2(f: fn()error!void) void {
const x = f();
}
fn bar2() (error{}!void) { }
test "error: Zero sized error set returned with value payload crash" {
_ = foo3(0);
_ = comptime foo3(0);
}
const Error = error{};
fn foo3(b: usize) Error!usize {
return b;
}
test "error: Infer error set from literals" {
_ = nullLiteral("n") catch |err| handleErrors(err);
_ = floatLiteral("n") catch |err| handleErrors(err);
_ = intLiteral("n") catch |err| handleErrors(err);
_ = comptime nullLiteral("n") catch |err| handleErrors(err);
_ = comptime floatLiteral("n") catch |err| handleErrors(err);
_ = comptime intLiteral("n") catch |err| handleErrors(err);
}
fn handleErrors(err: var) noreturn {
switch (err) {
error.T => {}
}
unreachable;
}
fn nullLiteral(str: []const u8) !?i64 {
if (str[0] == 'n')
return null;
return error.T;
}
fn floatLiteral(str: []const u8) !?f64 {
if (str[0] == 'n')
return 1.0;
return error.T;
}
fn intLiteral(str: []const u8) !?i64 {
if (str[0] == 'n')
return 1;
return error.T;
}

View File

@ -529,3 +529,10 @@ test "comptime shlWithOverflow" {
assert(ct_shifted == rt_shifted);
}
test "runtime 128 bit integer division" {
var a: u128 = 152313999999999991610955792383;
var b: u128 = 10000000000000000000;
var c = a / b;
assert(c == 15231399999);
}

View File

@ -3221,4 +3221,20 @@ pub fn addCases(cases: &tests.CompileErrorContext) void {
\\}
,
".tmp_source.zig:7:24: error: accessing union field 'Bar' while field 'Baz' is set");
cases.add("getting return type of generic function",
\\fn generic(a: var) void {}
\\comptime {
\\ _ = @typeOf(generic).ReturnType;
\\}
,
".tmp_source.zig:3:25: error: ReturnType has not been resolved because 'fn(var)var' is generic");
cases.add("getting @ArgType of generic function",
\\fn generic(a: var) void {}
\\comptime {
\\ _ = @ArgType(@typeOf(generic), 0);
\\}
,
".tmp_source.zig:3:36: error: @ArgType could not resolve the type of arg 0 because 'fn(var)var' is generic");
}