Merge remote-tracking branch 'origin/master' into llvm6

This commit is contained in:
Andrew Kelley 2018-02-23 12:56:41 -05:00
commit 9cfd7dea19
22 changed files with 991 additions and 183 deletions

View File

@ -429,6 +429,7 @@ set(ZIG_STD_FILES
"index.zig"
"io.zig"
"linked_list.zig"
"macho.zig"
"math/acos.zig"
"math/acosh.zig"
"math/asin.zig"

View File

@ -2278,17 +2278,16 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) {
return;
if (struct_type->data.structure.zero_bits_loop_flag) {
// If we get here it's due to recursion. From this we conclude that the struct is
// not zero bits, and if abi_alignment == 0 we further conclude that the first field
// is a pointer to this very struct, or a function pointer with parameters that
// reference such a type.
// If we get here it's due to recursion. This is a design flaw in the compiler,
// we should be able to still figure out alignment, but here we give up and say that
// the alignment is pointer width, then assert that the first field is within that
// alignment
struct_type->data.structure.zero_bits_known = true;
if (struct_type->data.structure.abi_alignment == 0) {
if (struct_type->data.structure.layout == ContainerLayoutPacked) {
struct_type->data.structure.abi_alignment = 1;
} else {
struct_type->data.structure.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref,
LLVMPointerType(LLVMInt8Type(), 0));
struct_type->data.structure.abi_alignment = LLVMABIAlignmentOfType(g->target_data_ref, LLVMPointerType(LLVMInt8Type(), 0));
}
}
return;
@ -2352,11 +2351,17 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) {
if (gen_field_index == 0) {
if (struct_type->data.structure.layout == ContainerLayoutPacked) {
struct_type->data.structure.abi_alignment = 1;
} else {
} else if (struct_type->data.structure.abi_alignment == 0) {
// Alignment of structs is the alignment of the first field, for now.
// TODO change this when we re-order struct fields (issue #168)
struct_type->data.structure.abi_alignment = get_abi_alignment(g, field_type);
assert(struct_type->data.structure.abi_alignment != 0);
} else {
// due to a design flaw in the compiler we assumed that alignment was
// pointer width, so we assert that this wasn't violated.
if (get_abi_alignment(g, field_type) > struct_type->data.structure.abi_alignment) {
zig_panic("compiler design flaw: incorrect alignment assumption");
}
}
}

View File

@ -4201,6 +4201,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c
continue;
}
ConstExprValue *field_val = &const_val->data.x_struct.fields[i];
assert(field_val->type != nullptr);
LLVMValueRef val = gen_const_val(g, field_val, "");
fields[type_struct_field->gen_index] = val;
make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(field_val->type, val);
@ -4373,6 +4374,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c
}
}
}
zig_unreachable();
case TypeTableEntryIdErrorUnion:
{
TypeTableEntry *payload_type = type_entry->data.error_union.payload_type;

View File

@ -4172,7 +4172,13 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod
buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol)));
}
// Temporarily set the name of the IrExecutable to the VariableDeclaration
// so that the struct or enum from the init expression inherits the name.
Buf *old_exec_name = irb->exec->name;
irb->exec->name = variable_declaration->symbol;
IrInstruction *init_value = ir_gen_node(irb, variable_declaration->expr, scope);
irb->exec->name = old_exec_name;
if (init_value == irb->codegen->invalid_instruction)
return init_value;
@ -6727,8 +6733,8 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, TypeTableEntry
result.id = ConstCastResultIdFnReturnType;
result.data.return_type = allocate_nonzero<ConstCastOnly>(1);
*result.data.return_type = child;
return result;
}
return result;
}
if (expected_type->data.fn.fn_type_id.param_count != actual_type->data.fn.fn_type_id.param_count) {
result.id = ConstCastResultIdFnArgCount;
@ -8183,7 +8189,7 @@ static IrInstruction *ir_get_ref(IrAnalyze *ira, IrInstruction *source_instructi
}
if (instr_is_comptime(value)) {
ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
ConstExprValue *val = ir_resolve_const(ira, value, UndefOk);
if (!val)
return ira->codegen->invalid_instruction;
bool final_is_const = (value->value.type->id == TypeTableEntryIdMetaType) ? is_const : true;
@ -9975,15 +9981,18 @@ static TypeTableEntry *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
ok = bigint_cmp(&rem_result, &mod_result) == CmpEQ;
}
} else {
if (float_cmp_zero(&op2->value) == CmpEQ) {
IrInstruction *casted_op2 = ir_implicit_cast(ira, op2, resolved_type);
if (casted_op2 == ira->codegen->invalid_instruction)
return ira->codegen->builtin_types.entry_invalid;
if (float_cmp_zero(&casted_op2->value) == CmpEQ) {
// the division by zero error will be caught later, but we don't
// have a remainder function ambiguity problem
ok = true;
} else {
ConstExprValue rem_result;
ConstExprValue mod_result;
float_rem(&rem_result, &op1->value, &op2->value);
float_mod(&mod_result, &op1->value, &op2->value);
float_rem(&rem_result, &op1->value, &casted_op2->value);
float_mod(&mod_result, &op1->value, &casted_op2->value);
ok = float_cmp(&rem_result, &mod_result) == CmpEQ;
}
}
@ -14928,6 +14937,7 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio
ConstExprValue *parent_ptr;
size_t abs_offset;
size_t rel_end;
bool ptr_is_undef = false;
if (array_type->id == TypeTableEntryIdArray) {
array_val = const_ptr_pointee(ira->codegen, &ptr_ptr->value);
abs_offset = 0;
@ -14935,7 +14945,12 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio
parent_ptr = nullptr;
} else if (array_type->id == TypeTableEntryIdPointer) {
parent_ptr = const_ptr_pointee(ira->codegen, &ptr_ptr->value);
switch (parent_ptr->data.x_ptr.special) {
if (parent_ptr->special == ConstValSpecialUndef) {
array_val = nullptr;
abs_offset = 0;
rel_end = SIZE_MAX;
ptr_is_undef = true;
} else switch (parent_ptr->data.x_ptr.special) {
case ConstPtrSpecialInvalid:
case ConstPtrSpecialDiscard:
zig_unreachable();
@ -14989,7 +15004,7 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio
}
uint64_t start_scalar = bigint_as_unsigned(&casted_start->value.data.x_bigint);
if (start_scalar > rel_end) {
if (!ptr_is_undef && start_scalar > rel_end) {
ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds slice"));
return ira->codegen->builtin_types.entry_invalid;
}
@ -15000,12 +15015,18 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio
} else {
end_scalar = rel_end;
}
if (end_scalar > rel_end) {
ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds slice"));
return ira->codegen->builtin_types.entry_invalid;
if (!ptr_is_undef) {
if (end_scalar > rel_end) {
ir_add_error(ira, &instruction->base, buf_sprintf("out of bounds slice"));
return ira->codegen->builtin_types.entry_invalid;
}
if (start_scalar > end_scalar) {
ir_add_error(ira, &instruction->base, buf_sprintf("slice start is greater than end"));
return ira->codegen->builtin_types.entry_invalid;
}
}
if (start_scalar > end_scalar) {
ir_add_error(ira, &instruction->base, buf_sprintf("slice start is greater than end"));
if (ptr_is_undef && start_scalar != end_scalar) {
ir_add_error(ira, &instruction->base, buf_sprintf("non-zero length slice of undefined pointer"));
return ira->codegen->builtin_types.entry_invalid;
}
@ -15021,25 +15042,27 @@ static TypeTableEntry *ir_analyze_instruction_slice(IrAnalyze *ira, IrInstructio
if (array_type->id == TypeTableEntryIdArray) {
ptr_val->data.x_ptr.mut = ptr_ptr->value.data.x_ptr.mut;
}
} else {
switch (parent_ptr->data.x_ptr.special) {
case ConstPtrSpecialInvalid:
case ConstPtrSpecialDiscard:
zig_unreachable();
case ConstPtrSpecialRef:
init_const_ptr_ref(ira->codegen, ptr_val,
parent_ptr->data.x_ptr.data.ref.pointee, slice_is_const(return_type));
break;
case ConstPtrSpecialBaseArray:
zig_unreachable();
case ConstPtrSpecialBaseStruct:
zig_panic("TODO");
case ConstPtrSpecialHardCodedAddr:
init_const_ptr_hard_coded_addr(ira->codegen, ptr_val,
parent_ptr->type->data.pointer.child_type,
parent_ptr->data.x_ptr.data.hard_coded_addr.addr + start_scalar,
slice_is_const(return_type));
}
} else if (ptr_is_undef) {
ptr_val->type = get_pointer_to_type(ira->codegen, parent_ptr->type->data.pointer.child_type,
slice_is_const(return_type));
ptr_val->special = ConstValSpecialUndef;
} else switch (parent_ptr->data.x_ptr.special) {
case ConstPtrSpecialInvalid:
case ConstPtrSpecialDiscard:
zig_unreachable();
case ConstPtrSpecialRef:
init_const_ptr_ref(ira->codegen, ptr_val,
parent_ptr->data.x_ptr.data.ref.pointee, slice_is_const(return_type));
break;
case ConstPtrSpecialBaseArray:
zig_unreachable();
case ConstPtrSpecialBaseStruct:
zig_panic("TODO");
case ConstPtrSpecialHardCodedAddr:
init_const_ptr_hard_coded_addr(ira->codegen, ptr_val,
parent_ptr->type->data.pointer.child_type,
parent_ptr->data.x_ptr.data.hard_coded_addr.addr + start_scalar,
slice_is_const(return_type));
}
ConstExprValue *len_val = &out_val->data.x_struct.fields[slice_len_index];

View File

@ -125,7 +125,6 @@ static const struct ZigKeyword zig_keywords[] = {
{"false", TokenIdKeywordFalse},
{"fn", TokenIdKeywordFn},
{"for", TokenIdKeywordFor},
{"goto", TokenIdKeywordGoto},
{"if", TokenIdKeywordIf},
{"inline", TokenIdKeywordInline},
{"nakedcc", TokenIdKeywordNakedCC},
@ -1542,7 +1541,6 @@ const char * token_name(TokenId id) {
case TokenIdKeywordFalse: return "false";
case TokenIdKeywordFn: return "fn";
case TokenIdKeywordFor: return "for";
case TokenIdKeywordGoto: return "goto";
case TokenIdKeywordIf: return "if";
case TokenIdKeywordInline: return "inline";
case TokenIdKeywordNakedCC: return "nakedcc";

View File

@ -66,7 +66,6 @@ enum TokenId {
TokenIdKeywordFalse,
TokenIdKeywordFn,
TokenIdKeywordFor,
TokenIdKeywordGoto,
TokenIdKeywordIf,
TokenIdKeywordInline,
TokenIdKeywordNakedCC,

View File

@ -5,6 +5,7 @@ const io = std.io;
const os = std.os;
const elf = std.elf;
const DW = std.dwarf;
const macho = std.macho;
const ArrayList = std.ArrayList;
const builtin = @import("builtin");
@ -47,7 +48,7 @@ pub fn getSelfDebugInfo() !&ElfStackTrace {
pub fn dumpCurrentStackTrace() void {
const stderr = getStderrStream() catch return;
const debug_info = getSelfDebugInfo() catch |err| {
stderr.print("Unable to open debug info: {}\n", @errorName(err)) catch return;
stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return;
return;
};
defer debug_info.close();
@ -61,7 +62,7 @@ pub fn dumpCurrentStackTrace() void {
pub fn dumpStackTrace(stack_trace: &const builtin.StackTrace) void {
const stderr = getStderrStream() catch return;
const debug_info = getSelfDebugInfo() catch |err| {
stderr.print("Unable to open debug info: {}\n", @errorName(err)) catch return;
stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", @errorName(err)) catch return;
return;
};
defer debug_info.close();
@ -180,43 +181,57 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: &mem.Allocator,
}
fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: var, address: usize) !void {
if (builtin.os == builtin.Os.windows) {
return error.UnsupportedDebugInfo;
}
// TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal
// at compile time. I'll call it issue #313
const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}";
const compile_unit = findCompileUnit(debug_info, address) catch {
try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
address);
return;
};
const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
defer line_info.deinit();
try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
line_info.file_name, line_info.line, line_info.column,
address, compile_unit_name);
if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
if (line_info.column == 0) {
try out_stream.write("\n");
} else {
{var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) {
try out_stream.writeByte(' ');
}}
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
}
} else |err| switch (err) {
error.EndOfFile => {},
else => return err,
}
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
switch (builtin.os) {
builtin.Os.windows => return error.UnsupportedDebugInfo,
builtin.Os.macosx => {
// TODO(bnoordhuis) It's theoretically possible to obtain the
// compilation unit from the symbtab but it's not that useful
// in practice because the compiler dumps everything in a single
// object file. Future improvement: use external dSYM data when
// available.
const unknown = macho.Symbol { .name = "???", .address = address };
const symbol = debug_info.symbol_table.search(address) ?? &unknown;
try out_stream.print(WHITE ++ "{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n",
symbol.name, address);
},
else => {
const compile_unit = findCompileUnit(debug_info, address) catch {
try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n",
address);
return;
};
const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name);
if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| {
defer line_info.deinit();
try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++
DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n",
line_info.file_name, line_info.line, line_info.column,
address, compile_unit_name);
if (printLineFromFile(debug_info.allocator(), out_stream, line_info)) {
if (line_info.column == 0) {
try out_stream.write("\n");
} else {
{var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) {
try out_stream.writeByte(' ');
}}
try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n");
}
} else |err| switch (err) {
error.EndOfFile => {},
else => return err,
}
} else |err| switch (err) {
error.MissingDebugInfo, error.InvalidDebugInfo => {
try out_stream.print(ptr_hex ++ " in ??? ({})\n", address, compile_unit_name);
},
else => return err,
}
},
else => return err,
}
}
@ -224,6 +239,7 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
switch (builtin.object_format) {
builtin.ObjectFormat.elf => {
const st = try allocator.create(ElfStackTrace);
errdefer allocator.destroy(st);
*st = ElfStackTrace {
.self_exe_file = undefined,
.elf = undefined,
@ -249,12 +265,22 @@ pub fn openSelfDebugInfo(allocator: &mem.Allocator) !&ElfStackTrace {
try scanAllCompileUnits(st);
return st;
},
builtin.ObjectFormat.macho => {
var exe_file = try os.openSelfExe();
defer exe_file.close();
const st = try allocator.create(ElfStackTrace);
errdefer allocator.destroy(st);
*st = ElfStackTrace {
.symbol_table = try macho.loadSymbols(allocator, &io.FileInStream.init(&exe_file)),
};
return st;
},
builtin.ObjectFormat.coff => {
return error.TodoSupportCoffDebugInfo;
},
builtin.ObjectFormat.macho => {
return error.TodoSupportMachoDebugInfo;
},
builtin.ObjectFormat.wasm => {
return error.TodoSupportCOFFDebugInfo;
},
@ -297,31 +323,40 @@ fn printLineFromFile(allocator: &mem.Allocator, out_stream: var, line_info: &con
}
}
pub const ElfStackTrace = struct {
self_exe_file: os.File,
elf: elf.Elf,
debug_info: &elf.SectionHeader,
debug_abbrev: &elf.SectionHeader,
debug_str: &elf.SectionHeader,
debug_line: &elf.SectionHeader,
debug_ranges: ?&elf.SectionHeader,
abbrev_table_list: ArrayList(AbbrevTableHeader),
compile_unit_list: ArrayList(CompileUnit),
pub const ElfStackTrace = switch (builtin.os) {
builtin.Os.macosx => struct {
symbol_table: macho.SymbolTable,
pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
return self.abbrev_table_list.allocator;
}
pub fn close(self: &ElfStackTrace) void {
self.symbol_table.deinit();
}
},
else => struct {
self_exe_file: os.File,
elf: elf.Elf,
debug_info: &elf.SectionHeader,
debug_abbrev: &elf.SectionHeader,
debug_str: &elf.SectionHeader,
debug_line: &elf.SectionHeader,
debug_ranges: ?&elf.SectionHeader,
abbrev_table_list: ArrayList(AbbrevTableHeader),
compile_unit_list: ArrayList(CompileUnit),
pub fn readString(self: &ElfStackTrace) ![]u8 {
var in_file_stream = io.FileInStream.init(&self.self_exe_file);
const in_stream = &in_file_stream.stream;
return readStringRaw(self.allocator(), in_stream);
}
pub fn allocator(self: &const ElfStackTrace) &mem.Allocator {
return self.abbrev_table_list.allocator;
}
pub fn close(self: &ElfStackTrace) void {
self.self_exe_file.close();
self.elf.close();
}
pub fn readString(self: &ElfStackTrace) ![]u8 {
var in_file_stream = io.FileInStream.init(&self.self_exe_file);
const in_stream = &in_file_stream.stream;
return readStringRaw(self.allocator(), in_stream);
}
pub fn close(self: &ElfStackTrace) void {
self.self_exe_file.close();
self.elf.close();
}
},
};
const PcRange = struct {

View File

@ -550,12 +550,6 @@ test "parse unsigned comptime" {
}
}
// Dummy field because of https://github.com/zig-lang/zig/issues/557.
// At top level because of https://github.com/zig-lang/zig/issues/675.
const Struct = struct {
unused: u8,
};
test "fmt.format" {
{
var buf1: [32]u8 = undefined;
@ -588,6 +582,10 @@ test "fmt.format" {
assert(mem.eql(u8, result, "u3: 5\n"));
}
{
// Dummy field because of https://github.com/zig-lang/zig/issues/557.
const Struct = struct {
unused: u8,
};
var buf1: [32]u8 = undefined;
const value = Struct {
.unused = 42,

View File

@ -21,6 +21,7 @@ pub const endian = @import("endian.zig");
pub const fmt = @import("fmt/index.zig");
pub const heap = @import("heap.zig");
pub const io = @import("io.zig");
pub const macho = @import("macho.zig");
pub const math = @import("math/index.zig");
pub const mem = @import("mem.zig");
pub const net = @import("net.zig");
@ -51,6 +52,7 @@ test "std" {
_ = @import("endian.zig");
_ = @import("fmt/index.zig");
_ = @import("io.zig");
_ = @import("macho.zig");
_ = @import("math/index.zig");
_ = @import("mem.zig");
_ = @import("heap.zig");

170
std/macho.zig Normal file
View File

@ -0,0 +1,170 @@
const builtin = @import("builtin");
const std = @import("index.zig");
const io = std.io;
const mem = std.mem;
const MH_MAGIC_64 = 0xFEEDFACF;
const MH_PIE = 0x200000;
const LC_SYMTAB = 2;
const MachHeader64 = packed struct {
magic: u32,
cputype: u32,
cpusubtype: u32,
filetype: u32,
ncmds: u32,
sizeofcmds: u32,
flags: u32,
reserved: u32,
};
const LoadCommand = packed struct {
cmd: u32,
cmdsize: u32,
};
const SymtabCommand = packed struct {
symoff: u32,
nsyms: u32,
stroff: u32,
strsize: u32,
};
const Nlist64 = packed struct {
n_strx: u32,
n_type: u8,
n_sect: u8,
n_desc: u16,
n_value: u64,
};
pub const Symbol = struct {
name: []const u8,
address: u64,
fn addressLessThan(lhs: &const Symbol, rhs: &const Symbol) bool {
return lhs.address < rhs.address;
}
};
pub const SymbolTable = struct {
allocator: &mem.Allocator,
symbols: []const Symbol,
strings: []const u8,
// Doubles as an eyecatcher to calculate the PIE slide, see loadSymbols().
// Ideally we'd use _mh_execute_header because it's always at 0x100000000
// in the image but as it's located in a different section than executable
// code, its displacement is different.
pub fn deinit(self: &SymbolTable) void {
self.allocator.free(self.symbols);
self.symbols = []const Symbol {};
self.allocator.free(self.strings);
self.strings = []const u8 {};
}
pub fn search(self: &const SymbolTable, address: usize) ?&const Symbol {
var min: usize = 0;
var max: usize = self.symbols.len - 1; // Exclude sentinel.
while (min < max) {
const mid = min + (max - min) / 2;
const curr = &self.symbols[mid];
const next = &self.symbols[mid + 1];
if (address >= next.address) {
min = mid + 1;
} else if (address < curr.address) {
max = mid;
} else {
return curr;
}
}
return null;
}
};
pub fn loadSymbols(allocator: &mem.Allocator, in: &io.FileInStream) !SymbolTable {
var file = in.file;
try file.seekTo(0);
var hdr: MachHeader64 = undefined;
try readOneNoEof(in, MachHeader64, &hdr);
if (hdr.magic != MH_MAGIC_64) return error.MissingDebugInfo;
const is_pie = MH_PIE == (hdr.flags & MH_PIE);
var pos: usize = @sizeOf(@typeOf(hdr));
var ncmd: u32 = hdr.ncmds;
while (ncmd != 0) : (ncmd -= 1) {
try file.seekTo(pos);
var lc: LoadCommand = undefined;
try readOneNoEof(in, LoadCommand, &lc);
if (lc.cmd == LC_SYMTAB) break;
pos += lc.cmdsize;
} else {
return error.MissingDebugInfo;
}
var cmd: SymtabCommand = undefined;
try readOneNoEof(in, SymtabCommand, &cmd);
try file.seekTo(cmd.symoff);
var syms = try allocator.alloc(Nlist64, cmd.nsyms);
defer allocator.free(syms);
try readNoEof(in, Nlist64, syms);
try file.seekTo(cmd.stroff);
var strings = try allocator.alloc(u8, cmd.strsize);
errdefer allocator.free(strings);
try in.stream.readNoEof(strings);
var nsyms: usize = 0;
for (syms) |sym| if (isSymbol(sym)) nsyms += 1;
if (nsyms == 0) return error.MissingDebugInfo;
var symbols = try allocator.alloc(Symbol, nsyms + 1); // Room for sentinel.
errdefer allocator.free(symbols);
var pie_slide: usize = 0;
var nsym: usize = 0;
for (syms) |sym| {
if (!isSymbol(sym)) continue;
const start = sym.n_strx;
const end = ??mem.indexOfScalarPos(u8, strings, start, 0);
const name = strings[start..end];
const address = sym.n_value;
symbols[nsym] = Symbol { .name = name, .address = address };
nsym += 1;
if (is_pie and mem.eql(u8, name, "_SymbolTable_deinit")) {
pie_slide = @ptrToInt(SymbolTable.deinit) - address;
}
}
// Effectively a no-op, lld emits symbols in ascending order.
std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);
// Insert the sentinel. Since we don't know where the last function ends,
// we arbitrarily limit it to the start address + 4 KB.
const top = symbols[nsyms - 1].address + 4096;
symbols[nsyms] = Symbol { .name = "", .address = top };
if (pie_slide != 0) {
for (symbols) |*symbol| symbol.address += pie_slide;
}
return SymbolTable {
.allocator = allocator,
.symbols = symbols,
.strings = strings,
};
}
fn readNoEof(in: &io.FileInStream, comptime T: type, result: []T) !void {
return in.stream.readNoEof(([]u8)(result));
}
fn readOneNoEof(in: &io.FileInStream, comptime T: type, result: &T) !void {
return readNoEof(in, T, result[0..1]);
}
fn isSymbol(sym: &const Nlist64) bool {
return sym.n_value != 0 and sym.n_desc == 0;
}

View File

@ -42,6 +42,9 @@ pub const Allocator = struct {
fn alignedAlloc(self: &Allocator, comptime T: type, comptime alignment: u29,
n: usize) ![]align(alignment) T
{
if (n == 0) {
return (&align(alignment) T)(undefined)[0..0];
}
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);
@ -62,6 +65,10 @@ pub const Allocator = struct {
if (old_mem.len == 0) {
return self.alloc(T, n);
}
if (n == 0) {
self.free(old_mem);
return (&align(alignment) T)(undefined)[0..0];
}
const old_byte_slice = ([]u8)(old_mem);
const byte_count = math.mul(usize, @sizeOf(T), n) catch return Error.OutOfMemory;

View File

@ -329,6 +329,8 @@ pub const TIOCGPKT = 0x80045438;
pub const TIOCGPTLCK = 0x80045439;
pub const TIOCGEXCL = 0x80045440;
pub const EPOLL_CLOEXEC = O_CLOEXEC;
pub const EPOLL_CTL_ADD = 1;
pub const EPOLL_CTL_DEL = 2;
pub const EPOLL_CTL_MOD = 3;
@ -751,22 +753,31 @@ pub fn fstat(fd: i32, stat_buf: &Stat) usize {
return arch.syscall2(arch.SYS_fstat, usize(fd), @ptrToInt(stat_buf));
}
pub const epoll_data = u64;
pub const epoll_data = extern union {
ptr: usize,
fd: i32,
@"u32": u32,
@"u64": u64,
};
pub const epoll_event = extern struct {
events: u32,
data: epoll_data
data: epoll_data,
};
pub fn epoll_create() usize {
return arch.syscall1(arch.SYS_epoll_create, usize(1));
return epoll_create1(0);
}
pub fn epoll_create1(flags: usize) usize {
return arch.syscall1(arch.SYS_epoll_create1, flags);
}
pub fn epoll_ctl(epoll_fd: i32, op: i32, fd: i32, ev: &epoll_event) usize {
return arch.syscall4(arch.SYS_epoll_ctl, usize(epoll_fd), usize(op), usize(fd), @ptrToInt(ev));
}
pub fn epoll_wait(epoll_fd: i32, events: &epoll_event, maxevents: i32, timeout: i32) usize {
pub fn epoll_wait(epoll_fd: i32, events: &epoll_event, maxevents: u32, timeout: i32) usize {
return arch.syscall4(arch.SYS_epoll_wait, usize(epoll_fd), @ptrToInt(events), usize(maxevents), usize(timeout));
}

View File

@ -25,7 +25,7 @@ test "timer" {
var event = linux.epoll_event {
.events = linux.EPOLLIN | linux.EPOLLOUT | linux.EPOLLET,
.data = 0
.data = linux.epoll_data { .ptr = 0 },
};
err = linux.epoll_ctl(i32(epoll_fd), linux.EPOLL_CTL_ADD, i32(timer_fd), &event);

View File

@ -6,6 +6,7 @@ const mem = std.mem;
pub const Node = struct {
id: Id,
comment: ?&NodeLineComment,
pub const Id = enum {
Root,
@ -18,7 +19,9 @@ pub const Node = struct {
PrefixOp,
IntegerLiteral,
FloatLiteral,
StringLiteral,
BuiltinCall,
LineComment,
};
pub fn iterate(base: &Node, index: usize) ?&Node {
@ -33,7 +36,45 @@ pub const Node = struct {
Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).iterate(index),
Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).iterate(index),
Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).iterate(index),
Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).iterate(index),
Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).iterate(index),
Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).iterate(index),
};
}
pub fn firstToken(base: &Node) Token {
return switch (base.id) {
Id.Root => @fieldParentPtr(NodeRoot, "base", base).firstToken(),
Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).firstToken(),
Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).firstToken(),
Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).firstToken(),
Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).firstToken(),
Id.Block => @fieldParentPtr(NodeBlock, "base", base).firstToken(),
Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).firstToken(),
Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).firstToken(),
Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).firstToken(),
Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).firstToken(),
Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).firstToken(),
Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).firstToken(),
Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).firstToken(),
};
}
pub fn lastToken(base: &Node) Token {
return switch (base.id) {
Id.Root => @fieldParentPtr(NodeRoot, "base", base).lastToken(),
Id.VarDecl => @fieldParentPtr(NodeVarDecl, "base", base).lastToken(),
Id.Identifier => @fieldParentPtr(NodeIdentifier, "base", base).lastToken(),
Id.FnProto => @fieldParentPtr(NodeFnProto, "base", base).lastToken(),
Id.ParamDecl => @fieldParentPtr(NodeParamDecl, "base", base).lastToken(),
Id.Block => @fieldParentPtr(NodeBlock, "base", base).lastToken(),
Id.InfixOp => @fieldParentPtr(NodeInfixOp, "base", base).lastToken(),
Id.PrefixOp => @fieldParentPtr(NodePrefixOp, "base", base).lastToken(),
Id.IntegerLiteral => @fieldParentPtr(NodeIntegerLiteral, "base", base).lastToken(),
Id.FloatLiteral => @fieldParentPtr(NodeFloatLiteral, "base", base).lastToken(),
Id.StringLiteral => @fieldParentPtr(NodeStringLiteral, "base", base).lastToken(),
Id.BuiltinCall => @fieldParentPtr(NodeBuiltinCall, "base", base).lastToken(),
Id.LineComment => @fieldParentPtr(NodeLineComment, "base", base).lastToken(),
};
}
};
@ -41,6 +82,7 @@ pub const Node = struct {
pub const NodeRoot = struct {
base: Node,
decls: ArrayList(&Node),
eof_token: Token,
pub fn iterate(self: &NodeRoot, index: usize) ?&Node {
if (index < self.decls.len) {
@ -48,6 +90,14 @@ pub const NodeRoot = struct {
}
return null;
}
pub fn firstToken(self: &NodeRoot) Token {
return if (self.decls.len == 0) self.eof_token else self.decls.at(0).firstToken();
}
pub fn lastToken(self: &NodeRoot) Token {
return if (self.decls.len == 0) self.eof_token else self.decls.at(self.decls.len - 1).lastToken();
}
};
pub const NodeVarDecl = struct {
@ -62,6 +112,7 @@ pub const NodeVarDecl = struct {
type_node: ?&Node,
align_node: ?&Node,
init_node: ?&Node,
semicolon_token: Token,
pub fn iterate(self: &NodeVarDecl, index: usize) ?&Node {
var i = index;
@ -83,6 +134,18 @@ pub const NodeVarDecl = struct {
return null;
}
pub fn firstToken(self: &NodeVarDecl) Token {
if (self.visib_token) |visib_token| return visib_token;
if (self.comptime_token) |comptime_token| return comptime_token;
if (self.extern_token) |extern_token| return extern_token;
assert(self.lib_name == null);
return self.mut_token;
}
pub fn lastToken(self: &NodeVarDecl) Token {
return self.semicolon_token;
}
};
pub const NodeIdentifier = struct {
@ -92,6 +155,14 @@ pub const NodeIdentifier = struct {
pub fn iterate(self: &NodeIdentifier, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &NodeIdentifier) Token {
return self.name_token;
}
pub fn lastToken(self: &NodeIdentifier) Token {
return self.name_token;
}
};
pub const NodeFnProto = struct {
@ -100,7 +171,7 @@ pub const NodeFnProto = struct {
fn_token: Token,
name_token: ?Token,
params: ArrayList(&Node),
return_type: &Node,
return_type: ReturnType,
var_args_token: ?Token,
extern_token: ?Token,
inline_token: ?Token,
@ -109,6 +180,12 @@ pub const NodeFnProto = struct {
lib_name: ?&Node, // populated if this is an extern declaration
align_expr: ?&Node, // populated if align(A) is present
pub const ReturnType = union(enum) {
Explicit: &Node,
Infer: Token,
InferErrorSet: &Node,
};
pub fn iterate(self: &NodeFnProto, index: usize) ?&Node {
var i = index;
@ -117,8 +194,18 @@ pub const NodeFnProto = struct {
i -= 1;
}
if (i < 1) return self.return_type;
i -= 1;
switch (self.return_type) {
// TODO allow this and next prong to share bodies since the types are the same
ReturnType.Explicit => |node| {
if (i < 1) return node;
i -= 1;
},
ReturnType.InferErrorSet => |node| {
if (i < 1) return node;
i -= 1;
},
ReturnType.Infer => {},
}
if (self.align_expr) |align_expr| {
if (i < 1) return align_expr;
@ -135,6 +222,25 @@ pub const NodeFnProto = struct {
return null;
}
pub fn firstToken(self: &NodeFnProto) Token {
if (self.visib_token) |visib_token| return visib_token;
if (self.extern_token) |extern_token| return extern_token;
assert(self.lib_name == null);
if (self.inline_token) |inline_token| return inline_token;
if (self.cc_token) |cc_token| return cc_token;
return self.fn_token;
}
pub fn lastToken(self: &NodeFnProto) Token {
if (self.body_node) |body_node| return body_node.lastToken();
switch (self.return_type) {
// TODO allow this and next prong to share bodies since the types are the same
ReturnType.Explicit => |node| return node.lastToken(),
ReturnType.InferErrorSet => |node| return node.lastToken(),
ReturnType.Infer => |token| return token,
}
}
};
pub const NodeParamDecl = struct {
@ -153,6 +259,18 @@ pub const NodeParamDecl = struct {
return null;
}
pub fn firstToken(self: &NodeParamDecl) Token {
if (self.comptime_token) |comptime_token| return comptime_token;
if (self.noalias_token) |noalias_token| return noalias_token;
if (self.name_token) |name_token| return name_token;
return self.type_node.firstToken();
}
pub fn lastToken(self: &NodeParamDecl) Token {
if (self.var_args_token) |var_args_token| return var_args_token;
return self.type_node.lastToken();
}
};
pub const NodeBlock = struct {
@ -169,6 +287,14 @@ pub const NodeBlock = struct {
return null;
}
pub fn firstToken(self: &NodeBlock) Token {
return self.begin_token;
}
pub fn lastToken(self: &NodeBlock) Token {
return self.end_token;
}
};
pub const NodeInfixOp = struct {
@ -181,6 +307,7 @@ pub const NodeInfixOp = struct {
const InfixOp = enum {
EqualEqual,
BangEqual,
Period,
};
pub fn iterate(self: &NodeInfixOp, index: usize) ?&Node {
@ -190,8 +317,9 @@ pub const NodeInfixOp = struct {
i -= 1;
switch (self.op) {
InfixOp.EqualEqual => {},
InfixOp.BangEqual => {},
InfixOp.EqualEqual,
InfixOp.BangEqual,
InfixOp.Period => {},
}
if (i < 1) return self.rhs;
@ -199,6 +327,14 @@ pub const NodeInfixOp = struct {
return null;
}
pub fn firstToken(self: &NodeInfixOp) Token {
return self.lhs.firstToken();
}
pub fn lastToken(self: &NodeInfixOp) Token {
return self.rhs.lastToken();
}
};
pub const NodePrefixOp = struct {
@ -209,6 +345,7 @@ pub const NodePrefixOp = struct {
const PrefixOp = union(enum) {
Return,
Try,
AddrOf: AddrOfInfo,
};
const AddrOfInfo = struct {
@ -223,7 +360,8 @@ pub const NodePrefixOp = struct {
var i = index;
switch (self.op) {
PrefixOp.Return => {},
PrefixOp.Return,
PrefixOp.Try => {},
PrefixOp.AddrOf => |addr_of_info| {
if (addr_of_info.align_expr) |align_expr| {
if (i < 1) return align_expr;
@ -237,6 +375,14 @@ pub const NodePrefixOp = struct {
return null;
}
pub fn firstToken(self: &NodePrefixOp) Token {
return self.op_token;
}
pub fn lastToken(self: &NodePrefixOp) Token {
return self.rhs.lastToken();
}
};
pub const NodeIntegerLiteral = struct {
@ -246,6 +392,14 @@ pub const NodeIntegerLiteral = struct {
pub fn iterate(self: &NodeIntegerLiteral, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &NodeIntegerLiteral) Token {
return self.token;
}
pub fn lastToken(self: &NodeIntegerLiteral) Token {
return self.token;
}
};
pub const NodeFloatLiteral = struct {
@ -255,12 +409,21 @@ pub const NodeFloatLiteral = struct {
pub fn iterate(self: &NodeFloatLiteral, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &NodeFloatLiteral) Token {
return self.token;
}
pub fn lastToken(self: &NodeFloatLiteral) Token {
return self.token;
}
};
pub const NodeBuiltinCall = struct {
base: Node,
builtin_token: Token,
params: ArrayList(&Node),
rparen_token: Token,
pub fn iterate(self: &NodeBuiltinCall, index: usize) ?&Node {
var i = index;
@ -270,4 +433,46 @@ pub const NodeBuiltinCall = struct {
return null;
}
pub fn firstToken(self: &NodeBuiltinCall) Token {
return self.builtin_token;
}
pub fn lastToken(self: &NodeBuiltinCall) Token {
return self.rparen_token;
}
};
pub const NodeStringLiteral = struct {
base: Node,
token: Token,
pub fn iterate(self: &NodeStringLiteral, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &NodeStringLiteral) Token {
return self.token;
}
pub fn lastToken(self: &NodeStringLiteral) Token {
return self.token;
}
};
pub const NodeLineComment = struct {
base: Node,
lines: ArrayList(Token),
pub fn iterate(self: &NodeLineComment, index: usize) ?&Node {
return null;
}
pub fn firstToken(self: &NodeLineComment) Token {
return self.lines.at(0);
}
pub fn lastToken(self: &NodeLineComment) Token {
return self.lines.at(self.lines.len - 1);
}
};

View File

@ -18,6 +18,7 @@ pub const Parser = struct {
put_back_tokens: [2]Token,
put_back_count: usize,
source_file_name: []const u8,
pending_line_comment_node: ?&ast.NodeLineComment,
pub const Tree = struct {
root_node: &ast.NodeRoot,
@ -43,6 +44,7 @@ pub const Parser = struct {
.put_back_count = 0,
.source_file_name = source_file_name,
.utility_bytes = []align(utility_bytes_align) u8{},
.pending_line_comment_node = null,
};
}
@ -69,6 +71,11 @@ pub const Parser = struct {
}
};
const ExpectTokenSave = struct {
id: Token.Id,
ptr: &Token,
};
const State = union(enum) {
TopLevel,
TopLevelExtern: ?Token,
@ -85,13 +92,17 @@ pub const Parser = struct {
VarDeclAlign: &ast.NodeVarDecl,
VarDeclEq: &ast.NodeVarDecl,
ExpectToken: @TagType(Token.Id),
ExpectTokenSave: ExpectTokenSave,
FnProto: &ast.NodeFnProto,
FnProtoAlign: &ast.NodeFnProto,
FnProtoReturnType: &ast.NodeFnProto,
ParamDecl: &ast.NodeFnProto,
ParamDeclComma,
FnDef: &ast.NodeFnProto,
Block: &ast.NodeBlock,
Statement: &ast.NodeBlock,
ExprListItemOrEnd: &ArrayList(&ast.Node),
ExprListCommaOrEnd: &ArrayList(&ast.Node),
};
/// Returns an AST tree, allocated with the parser's allocator.
@ -122,6 +133,33 @@ pub const Parser = struct {
// warn("\n");
//}
// look for line comments
while (true) {
const token = self.getNextToken();
if (token.id == Token.Id.LineComment) {
const node = blk: {
if (self.pending_line_comment_node) |comment_node| {
break :blk comment_node;
} else {
const comment_node = try arena.create(ast.NodeLineComment);
*comment_node = ast.NodeLineComment {
.base = ast.Node {
.id = ast.Node.Id.LineComment,
.comment = null,
},
.lines = ArrayList(Token).init(arena),
};
self.pending_line_comment_node = comment_node;
break :blk comment_node;
}
};
try node.lines.append(token);
continue;
}
self.putBackToken(token);
break;
}
// This gives us 1 free append that can't fail
const state = stack.pop();
@ -133,7 +171,10 @@ pub const Parser = struct {
stack.append(State { .TopLevelExtern = token }) catch unreachable;
continue;
},
Token.Id.Eof => return Tree {.root_node = root_node, .arena_allocator = arena_allocator},
Token.Id.Eof => {
root_node.eof_token = token;
return Tree {.root_node = root_node, .arena_allocator = arena_allocator};
},
else => {
self.putBackToken(token);
stack.append(State { .TopLevelExtern = null }) catch unreachable;
@ -176,7 +217,7 @@ pub const Parser = struct {
stack.append(State.TopLevel) catch unreachable;
// TODO shouldn't need these casts
const fn_proto = try self.createAttachFnProto(arena, &root_node.decls, token,
ctx.extern_token, (?Token)(null), (?Token)(null), (?Token)(null));
ctx.extern_token, (?Token)(null), ctx.visib_token, (?Token)(null));
try stack.append(State { .FnDef = fn_proto });
try stack.append(State { .FnProto = fn_proto });
continue;
@ -228,13 +269,19 @@ pub const Parser = struct {
const token = self.getNextToken();
if (token.id == Token.Id.Equal) {
var_decl.eq_token = token;
stack.append(State { .ExpectToken = Token.Id.Semicolon }) catch unreachable;
stack.append(State {
.ExpectTokenSave = ExpectTokenSave {
.id = Token.Id.Semicolon,
.ptr = &var_decl.semicolon_token,
},
}) catch unreachable;
try stack.append(State {
.Expression = DestPtr {.NullableField = &var_decl.init_node},
});
continue;
}
if (token.id == Token.Id.Semicolon) {
var_decl.semicolon_token = token;
continue;
}
return self.parseError(token, "expected '=' or ';', found {}", @tagName(token.id));
@ -244,6 +291,11 @@ pub const Parser = struct {
continue;
},
State.ExpectTokenSave => |expect_token_save| {
*expect_token_save.ptr = try self.eatToken(expect_token_save.id);
continue;
},
State.Expression => |dest_ptr| {
// save the dest_ptr for later
stack.append(state) catch unreachable;
@ -261,6 +313,12 @@ pub const Parser = struct {
try stack.append(State.ExpectOperand);
continue;
},
Token.Id.Keyword_try => {
try stack.append(State { .PrefixOp = try self.createPrefixOp(arena, token,
ast.NodePrefixOp.PrefixOp.Try) });
try stack.append(State.ExpectOperand);
continue;
},
Token.Id.Ampersand => {
const prefix_op = try self.createPrefixOp(arena, token, ast.NodePrefixOp.PrefixOp{
.AddrOf = ast.NodePrefixOp.AddrOfInfo {
@ -297,6 +355,40 @@ pub const Parser = struct {
try stack.append(State.AfterOperand);
continue;
},
Token.Id.Builtin => {
const node = try arena.create(ast.NodeBuiltinCall);
*node = ast.NodeBuiltinCall {
.base = self.initNode(ast.Node.Id.BuiltinCall),
.builtin_token = token,
.params = ArrayList(&ast.Node).init(arena),
.rparen_token = undefined,
};
try stack.append(State {
.Operand = &node.base
});
try stack.append(State.AfterOperand);
try stack.append(State {.ExprListItemOrEnd = &node.params });
try stack.append(State {
.ExpectTokenSave = ExpectTokenSave {
.id = Token.Id.LParen,
.ptr = &node.rparen_token,
},
});
continue;
},
Token.Id.StringLiteral => {
const node = try arena.create(ast.NodeStringLiteral);
*node = ast.NodeStringLiteral {
.base = self.initNode(ast.Node.Id.StringLiteral),
.token = token,
};
try stack.append(State {
.Operand = &node.base
});
try stack.append(State.AfterOperand);
continue;
},
else => return self.parseError(token, "expected primary expression, found {}", @tagName(token.id)),
}
},
@ -321,6 +413,13 @@ pub const Parser = struct {
try stack.append(State.ExpectOperand);
continue;
},
Token.Id.Period => {
try stack.append(State {
.InfixOp = try self.createInfixOp(arena, token, ast.NodeInfixOp.InfixOp.Period)
});
try stack.append(State.ExpectOperand);
continue;
},
else => {
// no postfix/infix operator after this operand.
self.putBackToken(token);
@ -352,6 +451,29 @@ pub const Parser = struct {
}
},
State.ExprListItemOrEnd => |params| {
var token = self.getNextToken();
switch (token.id) {
Token.Id.RParen => continue,
else => {
self.putBackToken(token);
stack.append(State { .ExprListCommaOrEnd = params }) catch unreachable;
try stack.append(State { .Expression = DestPtr{.List = params} });
},
}
},
State.ExprListCommaOrEnd => |params| {
var token = self.getNextToken();
switch (token.id) {
Token.Id.Comma => {
stack.append(State { .ExprListItemOrEnd = params }) catch unreachable;
},
Token.Id.RParen => continue,
else => return self.parseError(token, "expected ',' or ')', found {}", @tagName(token.id)),
}
},
State.AddrOfModifiers => |addr_of_info| {
var token = self.getNextToken();
switch (token.id) {
@ -414,11 +536,37 @@ pub const Parser = struct {
}
self.putBackToken(token);
stack.append(State {
.TypeExpr = DestPtr {.Field = &fn_proto.return_type},
.FnProtoReturnType = fn_proto,
}) catch unreachable;
continue;
},
State.FnProtoReturnType => |fn_proto| {
const token = self.getNextToken();
switch (token.id) {
Token.Id.Keyword_var => {
fn_proto.return_type = ast.NodeFnProto.ReturnType { .Infer = token };
},
Token.Id.Bang => {
fn_proto.return_type = ast.NodeFnProto.ReturnType { .InferErrorSet = undefined };
stack.append(State {
.TypeExpr = DestPtr {.Field = &fn_proto.return_type.InferErrorSet},
}) catch unreachable;
},
else => {
self.putBackToken(token);
fn_proto.return_type = ast.NodeFnProto.ReturnType { .Explicit = undefined };
stack.append(State {
.TypeExpr = DestPtr {.Field = &fn_proto.return_type.Explicit},
}) catch unreachable;
},
}
if (token.id == Token.Id.Keyword_align) {
@panic("TODO fn proto align");
}
continue;
},
State.ParamDecl => |fn_proto| {
var token = self.getNextToken();
if (token.id == Token.Id.RParen) {
@ -539,17 +687,25 @@ pub const Parser = struct {
State.PrefixOp => unreachable,
State.Operand => unreachable,
}
@import("std").debug.panic("{}", @tagName(state));
//unreachable;
}
}
fn initNode(self: &Parser, id: ast.Node.Id) ast.Node {
if (self.pending_line_comment_node) |comment_node| {
self.pending_line_comment_node = null;
return ast.Node {.id = id, .comment = comment_node};
}
return ast.Node {.id = id, .comment = null };
}
fn createRoot(self: &Parser, arena: &mem.Allocator) !&ast.NodeRoot {
const node = try arena.create(ast.NodeRoot);
*node = ast.NodeRoot {
.base = ast.Node {.id = ast.Node.Id.Root},
.base = self.initNode(ast.Node.Id.Root),
.decls = ArrayList(&ast.Node).init(arena),
// initialized when we get the eof token
.eof_token = undefined,
};
return node;
}
@ -560,7 +716,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeVarDecl);
*node = ast.NodeVarDecl {
.base = ast.Node {.id = ast.Node.Id.VarDecl},
.base = self.initNode(ast.Node.Id.VarDecl),
.visib_token = *visib_token,
.mut_token = *mut_token,
.comptime_token = *comptime_token,
@ -572,6 +728,7 @@ pub const Parser = struct {
// initialized later
.name_token = undefined,
.eq_token = undefined,
.semicolon_token = undefined,
};
return node;
}
@ -582,7 +739,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeFnProto);
*node = ast.NodeFnProto {
.base = ast.Node {.id = ast.Node.Id.FnProto},
.base = self.initNode(ast.Node.Id.FnProto),
.visib_token = *visib_token,
.name_token = null,
.fn_token = *fn_token,
@ -603,7 +760,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeParamDecl);
*node = ast.NodeParamDecl {
.base = ast.Node {.id = ast.Node.Id.ParamDecl},
.base = self.initNode(ast.Node.Id.ParamDecl),
.comptime_token = null,
.noalias_token = null,
.name_token = null,
@ -617,7 +774,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeBlock);
*node = ast.NodeBlock {
.base = ast.Node {.id = ast.Node.Id.Block},
.base = self.initNode(ast.Node.Id.Block),
.begin_token = *begin_token,
.end_token = undefined,
.statements = ArrayList(&ast.Node).init(arena),
@ -629,7 +786,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeInfixOp);
*node = ast.NodeInfixOp {
.base = ast.Node {.id = ast.Node.Id.InfixOp},
.base = self.initNode(ast.Node.Id.InfixOp),
.op_token = *op_token,
.lhs = undefined,
.op = *op,
@ -642,7 +799,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodePrefixOp);
*node = ast.NodePrefixOp {
.base = ast.Node {.id = ast.Node.Id.PrefixOp},
.base = self.initNode(ast.Node.Id.PrefixOp),
.op_token = *op_token,
.op = *op,
.rhs = undefined,
@ -654,7 +811,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeIdentifier);
*node = ast.NodeIdentifier {
.base = ast.Node {.id = ast.Node.Id.Identifier},
.base = self.initNode(ast.Node.Id.Identifier),
.name_token = *name_token,
};
return node;
@ -664,7 +821,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeIntegerLiteral);
*node = ast.NodeIntegerLiteral {
.base = ast.Node {.id = ast.Node.Id.IntegerLiteral},
.base = self.initNode(ast.Node.Id.IntegerLiteral),
.token = *token,
};
return node;
@ -674,7 +831,7 @@ pub const Parser = struct {
const node = try arena.create(ast.NodeFloatLiteral);
*node = ast.NodeFloatLiteral {
.base = ast.Node {.id = ast.Node.Id.FloatLiteral},
.base = self.initNode(ast.Node.Id.FloatLiteral),
.token = *token,
};
return node;
@ -712,11 +869,11 @@ pub const Parser = struct {
fn parseError(self: &Parser, token: &const Token, comptime fmt: []const u8, args: ...) (error{ParseError}) {
const loc = self.tokenizer.getTokenLocation(token);
warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, loc.line + 1, loc.column + 1, args);
warn("{}:{}:{}: error: " ++ fmt ++ "\n", self.source_file_name, token.line + 1, token.column + 1, args);
warn("{}\n", self.tokenizer.buffer[loc.line_start..loc.line_end]);
{
var i: usize = 0;
while (i < loc.column) : (i += 1) {
while (i < token.column) : (i += 1) {
warn(" ");
}
}
@ -808,11 +965,26 @@ pub const Parser = struct {
defer self.deinitUtilityArrayList(stack);
{
try stack.append(RenderState { .Text = "\n"});
var i = root_node.decls.len;
while (i != 0) {
i -= 1;
const decl = root_node.decls.items[i];
try stack.append(RenderState {.TopLevelDecl = decl});
if (i != 0) {
try stack.append(RenderState {
.Text = blk: {
const prev_node = root_node.decls.at(i - 1);
const prev_line_index = prev_node.lastToken().line;
const this_line_index = decl.firstToken().line;
if (this_line_index - prev_line_index >= 2) {
break :blk "\n\n";
}
break :blk "\n";
},
});
}
}
}
@ -842,7 +1014,6 @@ pub const Parser = struct {
try stream.print("(");
try stack.append(RenderState { .Text = "\n" });
if (fn_proto.body_node == null) {
try stack.append(RenderState { .Text = ";" });
}
@ -860,7 +1031,6 @@ pub const Parser = struct {
},
ast.Node.Id.VarDecl => {
const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", decl);
try stack.append(RenderState { .Text = "\n"});
try stack.append(RenderState { .VarDecl = var_decl});
},
@ -927,19 +1097,35 @@ pub const Parser = struct {
},
ast.Node.Id.Block => {
const block = @fieldParentPtr(ast.NodeBlock, "base", base);
try stream.write("{");
try stack.append(RenderState { .Text = "}"});
try stack.append(RenderState.PrintIndent);
try stack.append(RenderState { .Indent = indent});
try stack.append(RenderState { .Text = "\n"});
var i = block.statements.len;
while (i != 0) {
i -= 1;
const statement_node = block.statements.items[i];
try stack.append(RenderState { .Statement = statement_node});
if (block.statements.len == 0) {
try stream.write("{}");
} else {
try stream.write("{");
try stack.append(RenderState { .Text = "}"});
try stack.append(RenderState.PrintIndent);
try stack.append(RenderState { .Indent = indent + indent_delta});
try stack.append(RenderState { .Text = "\n" });
try stack.append(RenderState { .Indent = indent});
try stack.append(RenderState { .Text = "\n"});
var i = block.statements.len;
while (i != 0) {
i -= 1;
const statement_node = block.statements.items[i];
try stack.append(RenderState { .Statement = statement_node});
try stack.append(RenderState.PrintIndent);
try stack.append(RenderState { .Indent = indent + indent_delta});
try stack.append(RenderState {
.Text = blk: {
if (i != 0) {
const prev_statement_node = block.statements.items[i - 1];
const prev_line_index = prev_statement_node.lastToken().line;
const this_line_index = statement_node.firstToken().line;
if (this_line_index - prev_line_index >= 2) {
break :blk "\n\n";
}
}
break :blk "\n";
},
});
}
}
},
ast.Node.Id.InfixOp => {
@ -952,7 +1138,9 @@ pub const Parser = struct {
ast.NodeInfixOp.InfixOp.BangEqual => {
try stack.append(RenderState { .Text = " != "});
},
else => unreachable,
ast.NodeInfixOp.InfixOp.Period => {
try stack.append(RenderState { .Text = "."});
},
}
try stack.append(RenderState { .Expression = prefix_op_node.lhs });
},
@ -963,6 +1151,9 @@ pub const Parser = struct {
ast.NodePrefixOp.PrefixOp.Return => {
try stream.write("return ");
},
ast.NodePrefixOp.PrefixOp.Try => {
try stream.write("try ");
},
ast.NodePrefixOp.PrefixOp.AddrOf => |addr_of_info| {
try stream.write("&");
if (addr_of_info.volatile_token != null) {
@ -977,7 +1168,6 @@ pub const Parser = struct {
try stack.append(RenderState { .Expression = align_expr});
}
},
else => unreachable,
}
},
ast.Node.Id.IntegerLiteral => {
@ -988,7 +1178,30 @@ pub const Parser = struct {
const float_literal = @fieldParentPtr(ast.NodeFloatLiteral, "base", base);
try stream.print("{}", self.tokenizer.getTokenSlice(float_literal.token));
},
else => unreachable,
ast.Node.Id.StringLiteral => {
const string_literal = @fieldParentPtr(ast.NodeStringLiteral, "base", base);
try stream.print("{}", self.tokenizer.getTokenSlice(string_literal.token));
},
ast.Node.Id.BuiltinCall => {
const builtin_call = @fieldParentPtr(ast.NodeBuiltinCall, "base", base);
try stream.print("{}(", self.tokenizer.getTokenSlice(builtin_call.builtin_token));
try stack.append(RenderState { .Text = ")"});
var i = builtin_call.params.len;
while (i != 0) {
i -= 1;
const param_node = builtin_call.params.at(i);
try stack.append(RenderState { .Expression = param_node});
if (i != 0) {
try stack.append(RenderState { .Text = ", " });
}
}
},
ast.Node.Id.FnProto => @panic("TODO fn proto in an expression"),
ast.Node.Id.LineComment => @panic("TODO render line comment in an expression"),
ast.Node.Id.Root,
ast.Node.Id.VarDecl,
ast.Node.Id.ParamDecl => unreachable,
},
RenderState.FnProtoRParen => |fn_proto| {
try stream.print(")");
@ -1000,9 +1213,26 @@ pub const Parser = struct {
try stack.append(RenderState { .Expression = body_node});
try stack.append(RenderState { .Text = " "});
}
try stack.append(RenderState { .Expression = fn_proto.return_type});
switch (fn_proto.return_type) {
ast.NodeFnProto.ReturnType.Explicit => |node| {
try stack.append(RenderState { .Expression = node});
},
ast.NodeFnProto.ReturnType.Infer => {
try stream.print("var");
},
ast.NodeFnProto.ReturnType.InferErrorSet => |node| {
try stream.print("!");
try stack.append(RenderState { .Expression = node});
},
}
},
RenderState.Statement => |base| {
if (base.comment) |comment| {
for (comment.lines.toSliceConst()) |line_token| {
try stream.print("{}\n", self.tokenizer.getTokenSlice(line_token));
try stream.writeByteNTimes(' ', indent);
}
}
switch (base.id) {
ast.Node.Id.VarDecl => {
const var_decl = @fieldParentPtr(ast.NodeVarDecl, "base", base);
@ -1040,10 +1270,7 @@ pub const Parser = struct {
var fixed_buffer_mem: [100 * 1024]u8 = undefined;
fn testParse(source: []const u8, allocator: &mem.Allocator) ![]u8 {
var padded_source: [0x100]u8 = undefined;
std.mem.copy(u8, padded_source[0..source.len], source);
var tokenizer = Tokenizer.init(padded_source[0..source.len]);
var tokenizer = Tokenizer.init(source);
var parser = Parser.init(&tokenizer, allocator, "(memory buffer)");
defer parser.deinit();
@ -1098,6 +1325,43 @@ fn testCanonical(source: []const u8) !void {
}
test "zig fmt" {
try testCanonical(
\\const std = @import("std");
\\
\\pub fn main() !void {
\\ // If this program is run without stdout attached, exit with an error.
\\ // another comment
\\ var stdout_file = try std.io.getStdOut;
\\}
\\
);
try testCanonical(
\\const std = @import("std");
\\
\\pub fn main() !void {
\\ var stdout_file = try std.io.getStdOut;
\\ var stdout_file = try std.io.getStdOut;
\\
\\ var stdout_file = try std.io.getStdOut;
\\ var stdout_file = try std.io.getStdOut;
\\}
\\
);
try testCanonical(
\\pub fn main() !void {}
\\pub fn main() var {}
\\pub fn main() i32 {}
\\
);
try testCanonical(
\\const std = @import("std");
\\const std = @import();
\\
);
try testCanonical(
\\extern fn puts(s: &const u8) c_int;
\\

View File

@ -5,6 +5,8 @@ pub const Token = struct {
id: Id,
start: usize,
end: usize,
line: usize,
column: usize,
const KeywordId = struct {
bytes: []const u8,
@ -16,6 +18,7 @@ pub const Token = struct {
KeywordId{.bytes="and", .id = Id.Keyword_and},
KeywordId{.bytes="asm", .id = Id.Keyword_asm},
KeywordId{.bytes="break", .id = Id.Keyword_break},
KeywordId{.bytes="catch", .id = Id.Keyword_catch},
KeywordId{.bytes="comptime", .id = Id.Keyword_comptime},
KeywordId{.bytes="const", .id = Id.Keyword_const},
KeywordId{.bytes="continue", .id = Id.Keyword_continue},
@ -28,7 +31,6 @@ pub const Token = struct {
KeywordId{.bytes="false", .id = Id.Keyword_false},
KeywordId{.bytes="fn", .id = Id.Keyword_fn},
KeywordId{.bytes="for", .id = Id.Keyword_for},
KeywordId{.bytes="goto", .id = Id.Keyword_goto},
KeywordId{.bytes="if", .id = Id.Keyword_if},
KeywordId{.bytes="inline", .id = Id.Keyword_inline},
KeywordId{.bytes="nakedcc", .id = Id.Keyword_nakedcc},
@ -38,12 +40,14 @@ pub const Token = struct {
KeywordId{.bytes="packed", .id = Id.Keyword_packed},
KeywordId{.bytes="pub", .id = Id.Keyword_pub},
KeywordId{.bytes="return", .id = Id.Keyword_return},
KeywordId{.bytes="section", .id = Id.Keyword_section},
KeywordId{.bytes="stdcallcc", .id = Id.Keyword_stdcallcc},
KeywordId{.bytes="struct", .id = Id.Keyword_struct},
KeywordId{.bytes="switch", .id = Id.Keyword_switch},
KeywordId{.bytes="test", .id = Id.Keyword_test},
KeywordId{.bytes="this", .id = Id.Keyword_this},
KeywordId{.bytes="true", .id = Id.Keyword_true},
KeywordId{.bytes="try", .id = Id.Keyword_try},
KeywordId{.bytes="undefined", .id = Id.Keyword_undefined},
KeywordId{.bytes="union", .id = Id.Keyword_union},
KeywordId{.bytes="unreachable", .id = Id.Keyword_unreachable},
@ -95,10 +99,12 @@ pub const Token = struct {
AmpersandEqual,
IntegerLiteral,
FloatLiteral,
LineComment,
Keyword_align,
Keyword_and,
Keyword_asm,
Keyword_break,
Keyword_catch,
Keyword_comptime,
Keyword_const,
Keyword_continue,
@ -111,7 +117,6 @@ pub const Token = struct {
Keyword_false,
Keyword_fn,
Keyword_for,
Keyword_goto,
Keyword_if,
Keyword_inline,
Keyword_nakedcc,
@ -121,12 +126,14 @@ pub const Token = struct {
Keyword_packed,
Keyword_pub,
Keyword_return,
Keyword_section,
Keyword_stdcallcc,
Keyword_struct,
Keyword_switch,
Keyword_test,
Keyword_this,
Keyword_true,
Keyword_try,
Keyword_undefined,
Keyword_union,
Keyword_unreachable,
@ -140,21 +147,19 @@ pub const Token = struct {
pub const Tokenizer = struct {
buffer: []const u8,
index: usize,
line: usize,
column: usize,
pending_invalid_token: ?Token,
pub const Location = struct {
line: usize,
column: usize,
pub const LineLocation = struct {
line_start: usize,
line_end: usize,
};
pub fn getTokenLocation(self: &Tokenizer, token: &const Token) Location {
var loc = Location {
.line = 0,
.column = 0,
pub fn getTokenLocation(self: &Tokenizer, token: &const Token) LineLocation {
var loc = LineLocation {
.line_start = 0,
.line_end = 0,
.line_end = self.buffer.len,
};
for (self.buffer) |c, i| {
if (i == token.start) {
@ -163,11 +168,7 @@ pub const Tokenizer = struct {
return loc;
}
if (c == '\n') {
loc.line += 1;
loc.column = 0;
loc.line_start = i + 1;
} else {
loc.column += 1;
}
}
return loc;
@ -182,6 +183,8 @@ pub const Tokenizer = struct {
return Tokenizer {
.buffer = buffer,
.index = 0,
.line = 0,
.column = 0,
.pending_invalid_token = null,
};
}
@ -222,13 +225,21 @@ pub const Tokenizer = struct {
.id = Token.Id.Eof,
.start = self.index,
.end = undefined,
.line = self.line,
.column = self.column,
};
while (self.index < self.buffer.len) : (self.index += 1) {
while (self.index < self.buffer.len) {
const c = self.buffer[self.index];
switch (state) {
State.Start => switch (c) {
' ', '\n' => {
' ' => {
result.start = self.index + 1;
result.column += 1;
},
'\n' => {
result.start = self.index + 1;
result.line += 1;
result.column = 0;
},
'c' => {
state = State.C;
@ -460,7 +471,7 @@ pub const Tokenizer = struct {
State.Slash => switch (c) {
'/' => {
result.id = undefined;
result.id = Token.Id.LineComment;
state = State.LineComment;
},
else => {
@ -469,14 +480,7 @@ pub const Tokenizer = struct {
},
},
State.LineComment => switch (c) {
'\n' => {
state = State.Start;
result = Token {
.id = Token.Id.Eof,
.start = self.index + 1,
.end = undefined,
};
},
'\n' => break,
else => self.checkLiteralCharacter(),
},
State.Zero => switch (c) {
@ -543,6 +547,14 @@ pub const Tokenizer = struct {
else => break,
},
}
self.index += 1;
if (c == '\n') {
self.line += 1;
self.column = 0;
} else {
self.column += 1;
}
} else if (self.index == self.buffer.len) {
switch (state) {
State.Start,
@ -622,6 +634,8 @@ pub const Tokenizer = struct {
.id = Token.Id.Invalid,
.start = self.index,
.end = self.index + invalid_length,
.line = self.line,
.column = self.column,
};
}

View File

@ -35,6 +35,7 @@ comptime {
_ = @import("cases/slice.zig");
_ = @import("cases/struct.zig");
_ = @import("cases/struct_contains_slice_of_itself.zig");
_ = @import("cases/struct_contains_null_ptr_itself.zig");
_ = @import("cases/switch.zig");
_ = @import("cases/switch_prong_err_enum.zig");
_ = @import("cases/switch_prong_implicit_cast.zig");

View File

@ -388,3 +388,10 @@ test "string literal used as comptime slice is memoized" {
comptime assert(TypeWithCompTimeSlice(a).Node == TypeWithCompTimeSlice(b).Node);
comptime assert(TypeWithCompTimeSlice("link").Node == TypeWithCompTimeSlice("link").Node);
}
test "comptime slice of undefined pointer of length 0" {
const slice1 = (&i32)(undefined)[0..0];
assert(slice1.len == 0);
const slice2 = (&i32)(undefined)[100..100];
assert(slice2.len == 0);
}

View File

@ -394,4 +394,11 @@ fn test_f128() void {
fn should_not_be_zero(x: f128) void {
assert(x != 0.0);
}
}
test "comptime float rem int" {
comptime {
var x = f32(1) % 2;
assert(x == 1.0);
}
}

View File

@ -499,12 +499,29 @@ test "@canImplicitCast" {
}
test "@typeName" {
const Struct = struct {
};
const Union = union {
unused: u8,
};
const Enum = enum {
Unused,
};
comptime {
assert(mem.eql(u8, @typeName(i64), "i64"));
assert(mem.eql(u8, @typeName(&usize), "&usize"));
// https://github.com/zig-lang/zig/issues/675
assert(mem.eql(u8, @typeName(TypeFromFn(u8)), "TypeFromFn(u8)"));
assert(mem.eql(u8, @typeName(Struct), "Struct"));
assert(mem.eql(u8, @typeName(Union), "Union"));
assert(mem.eql(u8, @typeName(Enum), "Enum"));
}
}
fn TypeFromFn(comptime T: type) type {
return struct {};
}
test "volatile load and store" {
var number: i32 = 1234;
const ptr = (&volatile i32)(&number);

View File

@ -0,0 +1,22 @@
const std = @import("std");
const assert = std.debug.assert;
test "struct contains null pointer which contains original struct" {
var x: ?&NodeLineComment = null;
assert(x == null);
}
pub const Node = struct {
id: Id,
comment: ?&NodeLineComment,
pub const Id = enum {
Root,
LineComment,
};
};
pub const NodeLineComment = struct {
base: Node,
};

View File

@ -1,6 +1,26 @@
const tests = @import("tests.zig");
pub fn addCases(cases: &tests.CompileErrorContext) void {
cases.add("comptime slice of undefined pointer non-zero len",
\\export fn entry() void {
\\ const slice = (&i32)(undefined)[0..1];
\\}
,
".tmp_source.zig:2:36: error: non-zero length slice of undefined pointer");
cases.add("type checking function pointers",
\\fn a(b: fn (&const u8) void) void {
\\ b('a');
\\}
\\fn c(d: u8) void {
\\ @import("std").debug.warn("{c}\n", d);
\\}
\\export fn entry() void {
\\ a(c);
\\}
,
".tmp_source.zig:8:7: error: expected type 'fn(&const u8) void', found 'fn(u8) void'");
cases.add("no else prong on switch on global error set",
\\export fn entry() void {
\\ foo(error.A);