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

This commit is contained in:
Andrew Kelley 2018-07-31 14:36:27 -04:00
commit f804310d9f
29 changed files with 1843 additions and 392 deletions

View File

@ -74,44 +74,6 @@ that counts as "freestanding" for the purposes of this table.
* Reddit: [/r/zig](https://www.reddit.com/r/zig) * Reddit: [/r/zig](https://www.reddit.com/r/zig)
* Email list: [ziglang@googlegroups.com](https://groups.google.com/forum/#!forum/ziglang) * Email list: [ziglang@googlegroups.com](https://groups.google.com/forum/#!forum/ziglang)
### Wanted: Windows Developers
Flesh out the standard library for Windows, streamline Zig installation and
distribution for Windows. Work with LLVM and LLD teams to improve
PDB/CodeView/MSVC debugging. Implement stack traces for Windows in the MinGW
environment and the MSVC environment.
### Wanted: MacOS and iOS Developers
Flesh out the standard library for MacOS. Improve the MACH-O linker. Implement
stack traces for MacOS. Streamline the process of using Zig to build for
iOS.
### Wanted: Android Developers
Flesh out the standard library for Android. Streamline the process of using
Zig to build for Android and for depending on Zig code on Android.
### Wanted: Web Developers
Figure out what are the use cases for compiling Zig to WebAssembly. Create demo
projects with it and streamline experience for users trying to output
WebAssembly. Work on the documentation generator outputting useful searchable html
documentation. Create Zig modules for common web tasks such as WebSockets and gzip.
### Wanted: Embedded Developers
Flesh out the standard library for uncommon CPU architectures and OS targets.
Drive issue discussion for cross compiling and using Zig in constrained
or unusual environments.
### Wanted: Game Developers
Create cross platform Zig modules to compete with SDL and GLFW. Create an
OpenGL library that does not depend on libc. Drive the usability of Zig
for video games. Create a general purpose allocator that does not depend on
libc. Create demo games using Zig.
## Building ## Building
[![Build Status](https://travis-ci.org/ziglang/zig.svg?branch=master)](https://travis-ci.org/ziglang/zig) [![Build Status](https://travis-ci.org/ziglang/zig.svg?branch=master)](https://travis-ci.org/ziglang/zig)

View File

@ -45,6 +45,7 @@ pub fn build(b: *Builder) !void {
.c_header_files = nextValue(&index, build_info), .c_header_files = nextValue(&index, build_info),
.dia_guids_lib = nextValue(&index, build_info), .dia_guids_lib = nextValue(&index, build_info),
.llvm = undefined, .llvm = undefined,
.no_rosegment = b.option(bool, "no-rosegment", "Workaround to enable valgrind builds") orelse false,
}; };
ctx.llvm = try findLLVM(b, ctx.llvm_config_exe); ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
@ -228,6 +229,8 @@ fn configureStage2(b: *Builder, exe: var, ctx: Context) !void {
// TODO turn this into -Dextra-lib-path=/lib option // TODO turn this into -Dextra-lib-path=/lib option
exe.addLibPath("/lib"); exe.addLibPath("/lib");
exe.setNoRoSegment(ctx.no_rosegment);
exe.addIncludeDir("src"); exe.addIncludeDir("src");
exe.addIncludeDir(ctx.cmake_binary_dir); exe.addIncludeDir(ctx.cmake_binary_dir);
addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp"); addCppLib(b, exe, ctx.cmake_binary_dir, "zig_cpp");
@ -286,4 +289,5 @@ const Context = struct {
c_header_files: []const u8, c_header_files: []const u8,
dia_guids_lib: []const u8, dia_guids_lib: []const u8,
llvm: LibraryDep, llvm: LibraryDep,
no_rosegment: bool,
}; };

View File

@ -72,6 +72,9 @@ bool link(ArrayRef<const char *> Args, bool CanExitEarly, raw_ostream &Diag) {
exitLld(errorCount() ? 1 : 0); exitLld(errorCount() ? 1 : 0);
freeArena(); freeArena();
ObjFile::Instances.clear();
ImportFile::Instances.clear();
BitcodeFile::Instances.clear();
return !errorCount(); return !errorCount();
} }

View File

@ -134,6 +134,58 @@ pub fn main() void {
</p> </p>
{#see_also|Values|@import|Errors|Root Source File#} {#see_also|Values|@import|Errors|Root Source File#}
{#header_close#} {#header_close#}
{#header_open|Comments#}
{#code_begin|test|comments#}
const assert = @import("std").debug.assert;
test "comments" {
// Comments in Zig start with "//" and end at the next LF byte (end of line).
// The below line is a comment, and won't be executed.
//assert(false);
const x = true; // another comment
assert(x);
}
{#code_end#}
<p>
There are no multiline comments in Zig (e.g. like <code>/* */</code>
comments in C). This helps allow Zig to have the property that each line
of code can be tokenized out of context.
</p>
{#header_open|Doc comments#}
<p>
A doc comment is one that begins with exactly three slashes (i.e.
<code class="zig">///</code> but not <code class="zig">////</code>);
multiple doc comments in a row are merged together to form a multiline
doc comment. The doc comment documents whatever immediately follows it.
</p>
{#code_begin|syntax|doc_comments#}
/// A structure for storing a timestamp, with nanosecond precision (this is a
/// multiline doc comment).
const Timestamp = struct {
/// The number of seconds since the epoch (this is also a doc comment).
seconds: i64, // signed so we can represent pre-1970 (not a doc comment)
/// The number of nanoseconds past the second (doc comment again).
nanos: u32,
/// Returns a `Timestamp` struct representing the Unix epoch; that is, the
/// moment of 1970 Jan 1 00:00:00 UTC (this is a doc comment too).
pub fn unixEpoch() Timestamp {
return Timestamp{
.seconds = 0,
.nanos = 0,
};
}
};
{#code_end#}
<p>
Doc comments are only allowed in certain places; eventually, it will
become a compile error have a doc comment in an unexpected place, such as
in the middle of an expression, or just before a non-doc comment.
</p>
{#header_close#}
{#header_close#}
{#header_open|Values#} {#header_open|Values#}
{#code_begin|exe|values#} {#code_begin|exe|values#}
const std = @import("std"); const std = @import("std");
@ -4665,24 +4717,24 @@ async fn testSuspendBlock() void {
block, while the old thread continued executing the suspend block. block, while the old thread continued executing the suspend block.
</p> </p>
<p> <p>
However, if you use labeled <code>break</code> on the suspend block, the coroutine However, the coroutine can be directly resumed from the suspend block, in which case it
never returns to its resumer and continues executing. never returns to its resumer and continues executing.
</p> </p>
{#code_begin|test#} {#code_begin|test#}
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
test "break from suspend" { test "resume from suspend" {
var buf: [500]u8 = undefined; var buf: [500]u8 = undefined;
var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator; var a = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
var my_result: i32 = 1; var my_result: i32 = 1;
const p = try async<a> testBreakFromSuspend(&my_result); const p = try async<a> testResumeFromSuspend(&my_result);
cancel p; cancel p;
std.debug.assert(my_result == 2); std.debug.assert(my_result == 2);
} }
async fn testBreakFromSuspend(my_result: *i32) void { async fn testResumeFromSuspend(my_result: *i32) void {
s: suspend |p| { suspend |p| {
break :s; resume p;
} }
my_result.* += 1; my_result.* += 1;
suspend; suspend;
@ -7336,7 +7388,7 @@ Defer(body) = ("defer" | "deferror") body
IfExpression(body) = "if" "(" Expression ")" body option("else" BlockExpression(body)) IfExpression(body) = "if" "(" Expression ")" body option("else" BlockExpression(body))
SuspendExpression(body) = option(Symbol ":") "suspend" option(("|" Symbol "|" body)) SuspendExpression(body) = "suspend" option(("|" Symbol "|" body))
IfErrorExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body "else" "|" Symbol "|" BlockExpression(body) IfErrorExpression(body) = "if" "(" Expression ")" option("|" option("*") Symbol "|") body "else" "|" Symbol "|" BlockExpression(body)

View File

@ -6,6 +6,7 @@ const c = @import("c.zig");
const ir = @import("ir.zig"); const ir = @import("ir.zig");
const Value = @import("value.zig").Value; const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type; const Type = @import("type.zig").Type;
const Scope = @import("scope.zig").Scope;
const event = std.event; const event = std.event;
const assert = std.debug.assert; const assert = std.debug.assert;
const DW = std.dwarf; const DW = std.dwarf;
@ -156,7 +157,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
llvm_fn_type, llvm_fn_type,
) orelse return error.OutOfMemory; ) orelse return error.OutOfMemory;
const want_fn_safety = fn_val.block_scope.safety.get(ofile.comp); const want_fn_safety = fn_val.block_scope.?.safety.get(ofile.comp);
if (want_fn_safety and ofile.comp.haveLibC()) { if (want_fn_safety and ofile.comp.haveLibC()) {
try addLLVMFnAttr(ofile, llvm_fn, "sspstrong"); try addLLVMFnAttr(ofile, llvm_fn, "sspstrong");
try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4"); try addLLVMFnAttrStr(ofile, llvm_fn, "stack-protector-buffer-size", "4");
@ -168,6 +169,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
//} //}
const fn_type = fn_val.base.typ.cast(Type.Fn).?; const fn_type = fn_val.base.typ.cast(Type.Fn).?;
const fn_type_normal = &fn_type.key.data.Normal;
try addLLVMFnAttr(ofile, llvm_fn, "nounwind"); try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
//add_uwtable_attr(g, fn_table_entry->llvm_value); //add_uwtable_attr(g, fn_table_entry->llvm_value);
@ -209,7 +211,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
// addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); // addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull");
//} //}
const cur_ret_ptr = if (fn_type.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null; const cur_ret_ptr = if (fn_type_normal.return_type.handleIsPtr()) llvm.GetParam(llvm_fn, 0) else null;
// build all basic blocks // build all basic blocks
for (code.basic_block_list.toSlice()) |bb| { for (code.basic_block_list.toSlice()) |bb| {
@ -226,9 +228,86 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
// TODO set up error return tracing // TODO set up error return tracing
// TODO allocate temporary stack values // TODO allocate temporary stack values
// TODO create debug variable declarations for variables and allocate all local variables
const var_list = fn_type.non_key.Normal.variable_list.toSliceConst();
// create debug variable declarations for variables and allocate all local variables
for (var_list) |var_scope, i| {
const var_type = switch (var_scope.data) {
Scope.Var.Data.Const => unreachable,
Scope.Var.Data.Param => |param| param.typ,
};
// if (!type_has_bits(var->value->type)) {
// continue;
// }
// if (ir_get_var_is_comptime(var))
// continue;
// if (type_requires_comptime(var->value->type))
// continue;
// if (var->src_arg_index == SIZE_MAX) {
// var->value_ref = build_alloca(g, var->value->type, buf_ptr(&var->name), var->align_bytes);
// var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
// buf_ptr(&var->name), import->di_file, (unsigned)(var->decl_node->line + 1),
// var->value->type->di_type, !g->strip_debug_symbols, 0);
// } else {
// it's a parameter
// assert(var->gen_arg_index != SIZE_MAX);
// TypeTableEntry *gen_type;
// FnGenParamInfo *gen_info = &fn_table_entry->type_entry->data.fn.gen_param_info[var->src_arg_index];
if (var_type.handleIsPtr()) {
// if (gen_info->is_byval) {
// gen_type = var->value->type;
// } else {
// gen_type = gen_info->type;
// }
var_scope.data.Param.llvm_value = llvm.GetParam(llvm_fn, @intCast(c_uint, i));
} else {
// gen_type = var->value->type;
var_scope.data.Param.llvm_value = try renderAlloca(ofile, var_type, var_scope.name, Type.Pointer.Align.Abi);
}
// if (var->decl_node) {
// var->di_loc_var = ZigLLVMCreateParameterVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
// buf_ptr(&var->name), import->di_file,
// (unsigned)(var->decl_node->line + 1),
// gen_type->di_type, !g->strip_debug_symbols, 0, (unsigned)(var->gen_arg_index + 1));
// }
// }
}
// TODO finishing error return trace setup. we have to do this after all the allocas. // TODO finishing error return trace setup. we have to do this after all the allocas.
// TODO create debug variable declarations for parameters
// create debug variable declarations for parameters
// rely on the first variables in the variable_list being parameters.
//size_t next_var_i = 0;
for (fn_type.key.data.Normal.params) |param, i| {
//FnGenParamInfo *info = &fn_table_entry->type_entry->data.fn.gen_param_info[param_i];
//if (info->gen_index == SIZE_MAX)
// continue;
const scope_var = var_list[i];
//assert(variable->src_arg_index != SIZE_MAX);
//next_var_i += 1;
//assert(variable);
//assert(variable->value_ref);
if (!param.typ.handleIsPtr()) {
//clear_debug_source_node(g);
const llvm_param = llvm.GetParam(llvm_fn, @intCast(c_uint, i));
_ = renderStoreUntyped(
ofile,
llvm_param,
scope_var.data.Param.llvm_value,
Type.Pointer.Align.Abi,
Type.Pointer.Vol.Non,
);
}
//if (variable->decl_node) {
// gen_var_debug_decl(g, variable);
//}
}
for (code.basic_block_list.toSlice()) |current_block| { for (code.basic_block_list.toSlice()) |current_block| {
llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block); llvm.PositionBuilderAtEnd(ofile.builder, current_block.llvm_block);
@ -293,3 +372,79 @@ fn addLLVMFnAttrStr(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []cons
fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void { fn addLLVMFnAttrInt(ofile: *ObjectFile, fn_val: llvm.ValueRef, attr_name: []const u8, attr_val: u64) !void {
return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val); return addLLVMAttrInt(ofile, fn_val, @maxValue(llvm.AttributeIndex), attr_name, attr_val);
} }
fn renderLoadUntyped(
ofile: *ObjectFile,
ptr: llvm.ValueRef,
alignment: Type.Pointer.Align,
vol: Type.Pointer.Vol,
name: [*]const u8,
) !llvm.ValueRef {
const result = llvm.BuildLoad(ofile.builder, ptr, name) orelse return error.OutOfMemory;
switch (vol) {
Type.Pointer.Vol.Non => {},
Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1),
}
llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.GetElementType(llvm.TypeOf(ptr))));
return result;
}
fn renderLoad(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer, name: [*]const u8) !llvm.ValueRef {
return renderLoadUntyped(ofile, ptr, ptr_type.key.alignment, ptr_type.key.vol, name);
}
pub fn getHandleValue(ofile: *ObjectFile, ptr: llvm.ValueRef, ptr_type: *Type.Pointer) !?llvm.ValueRef {
const child_type = ptr_type.key.child_type;
if (!child_type.hasBits()) {
return null;
}
if (child_type.handleIsPtr()) {
return ptr;
}
return try renderLoad(ofile, ptr, ptr_type, c"");
}
pub fn renderStoreUntyped(
ofile: *ObjectFile,
value: llvm.ValueRef,
ptr: llvm.ValueRef,
alignment: Type.Pointer.Align,
vol: Type.Pointer.Vol,
) !llvm.ValueRef {
const result = llvm.BuildStore(ofile.builder, value, ptr) orelse return error.OutOfMemory;
switch (vol) {
Type.Pointer.Vol.Non => {},
Type.Pointer.Vol.Volatile => llvm.SetVolatile(result, 1),
}
llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm.TypeOf(value)));
return result;
}
pub fn renderStore(
ofile: *ObjectFile,
value: llvm.ValueRef,
ptr: llvm.ValueRef,
ptr_type: *Type.Pointer,
) !llvm.ValueRef {
return renderStoreUntyped(ofile, value, ptr, ptr_type.key.alignment, ptr_type.key.vol);
}
pub fn renderAlloca(
ofile: *ObjectFile,
var_type: *Type,
name: []const u8,
alignment: Type.Pointer.Align,
) !llvm.ValueRef {
const llvm_var_type = try var_type.getLlvmType(ofile.arena, ofile.context);
const name_with_null = try std.cstr.addNullByte(ofile.arena, name);
const result = llvm.BuildAlloca(ofile.builder, llvm_var_type, name_with_null.ptr) orelse return error.OutOfMemory;
llvm.SetAlignment(result, resolveAlign(ofile, alignment, llvm_var_type));
return result;
}
pub fn resolveAlign(ofile: *ObjectFile, alignment: Type.Pointer.Align, llvm_type: llvm.TypeRef) u32 {
return switch (alignment) {
Type.Pointer.Align.Abi => return llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, llvm_type),
Type.Pointer.Align.Override => |a| a,
};
}

View File

@ -35,6 +35,7 @@ const CInt = @import("c_int.zig").CInt;
pub const EventLoopLocal = struct { pub const EventLoopLocal = struct {
loop: *event.Loop, loop: *event.Loop,
llvm_handle_pool: std.atomic.Stack(llvm.ContextRef), llvm_handle_pool: std.atomic.Stack(llvm.ContextRef),
lld_lock: event.Lock,
/// TODO pool these so that it doesn't have to lock /// TODO pool these so that it doesn't have to lock
prng: event.Locked(std.rand.DefaultPrng), prng: event.Locked(std.rand.DefaultPrng),
@ -55,6 +56,7 @@ pub const EventLoopLocal = struct {
return EventLoopLocal{ return EventLoopLocal{
.loop = loop, .loop = loop,
.lld_lock = event.Lock.init(loop),
.llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(), .llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(),
.prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)), .prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)),
.native_libc = event.Future(LibCInstallation).init(loop), .native_libc = event.Future(LibCInstallation).init(loop),
@ -63,6 +65,7 @@ pub const EventLoopLocal = struct {
/// Must be called only after EventLoop.run completes. /// Must be called only after EventLoop.run completes.
fn deinit(self: *EventLoopLocal) void { fn deinit(self: *EventLoopLocal) void {
self.lld_lock.deinit();
while (self.llvm_handle_pool.pop()) |node| { while (self.llvm_handle_pool.pop()) |node| {
c.LLVMContextDispose(node.data); c.LLVMContextDispose(node.data);
self.loop.allocator.destroy(node); self.loop.allocator.destroy(node);
@ -220,12 +223,14 @@ pub const Compilation = struct {
int_type_table: event.Locked(IntTypeTable), int_type_table: event.Locked(IntTypeTable),
array_type_table: event.Locked(ArrayTypeTable), array_type_table: event.Locked(ArrayTypeTable),
ptr_type_table: event.Locked(PtrTypeTable), ptr_type_table: event.Locked(PtrTypeTable),
fn_type_table: event.Locked(FnTypeTable),
c_int_types: [CInt.list.len]*Type.Int, c_int_types: [CInt.list.len]*Type.Int,
const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql); const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql);
const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql); const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql);
const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql); const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql);
const FnTypeTable = std.HashMap(*const Type.Fn.Key, *Type.Fn, Type.Fn.Key.hash, Type.Fn.Key.eql);
const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8); const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8);
const CompileErrList = std.ArrayList(*Msg); const CompileErrList = std.ArrayList(*Msg);
@ -384,6 +389,7 @@ pub const Compilation = struct {
.int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)), .int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)),
.array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)), .array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)),
.ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)), .ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)),
.fn_type_table = event.Locked(FnTypeTable).init(loop, FnTypeTable.init(loop.allocator)),
.c_int_types = undefined, .c_int_types = undefined,
.meta_type = undefined, .meta_type = undefined,
@ -414,6 +420,7 @@ pub const Compilation = struct {
comp.int_type_table.private_data.deinit(); comp.int_type_table.private_data.deinit();
comp.array_type_table.private_data.deinit(); comp.array_type_table.private_data.deinit();
comp.ptr_type_table.private_data.deinit(); comp.ptr_type_table.private_data.deinit();
comp.fn_type_table.private_data.deinit();
comp.arena_allocator.deinit(); comp.arena_allocator.deinit();
comp.loop.allocator.destroy(comp); comp.loop.allocator.destroy(comp);
} }
@ -1160,13 +1167,48 @@ async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
fn_decl.value = Decl.Fn.Val{ .Fn = fn_val }; fn_decl.value = Decl.Fn.Val{ .Fn = fn_val };
symbol_name_consumed = true; symbol_name_consumed = true;
// Define local parameter variables
const root_scope = fn_decl.base.findRootScope();
for (fn_type.key.data.Normal.params) |param, i| {
//AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i);
const param_decl = @fieldParentPtr(ast.Node.ParamDecl, "base", fn_decl.fn_proto.params.at(i).*);
const name_token = param_decl.name_token orelse {
try comp.addCompileError(root_scope, Span{
.first = param_decl.firstToken(),
.last = param_decl.type_node.firstToken(),
}, "missing parameter name");
return error.SemanticAnalysisFailed;
};
const param_name = root_scope.tree.tokenSlice(name_token);
// if (is_noalias && get_codegen_ptr_type(param_type) == nullptr) {
// add_node_error(g, param_decl_node, buf_sprintf("noalias on non-pointer parameter"));
// }
// TODO check for shadowing
const var_scope = try Scope.Var.createParam(
comp,
fn_val.child_scope,
param_name,
&param_decl.base,
i,
param.typ,
);
fn_val.child_scope = &var_scope.base;
try fn_type.non_key.Normal.variable_list.append(var_scope);
}
const analyzed_code = try await (async comp.genAndAnalyzeCode( const analyzed_code = try await (async comp.genAndAnalyzeCode(
&fndef_scope.base, fn_val.child_scope,
body_node, body_node,
fn_type.return_type, fn_type.key.data.Normal.return_type,
) catch unreachable); ) catch unreachable);
errdefer analyzed_code.destroy(comp.gpa()); errdefer analyzed_code.destroy(comp.gpa());
assert(fn_val.block_scope != null);
// Kick off rendering to LLVM module, but it doesn't block the fn decl // Kick off rendering to LLVM module, but it doesn't block the fn decl
// analysis from being complete. // analysis from being complete.
try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code); try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
@ -1199,14 +1241,13 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn
var params = ArrayList(Type.Fn.Param).init(comp.gpa()); var params = ArrayList(Type.Fn.Param).init(comp.gpa());
var params_consumed = false; var params_consumed = false;
defer if (params_consumed) { defer if (!params_consumed) {
for (params.toSliceConst()) |param| { for (params.toSliceConst()) |param| {
param.typ.base.deref(comp); param.typ.base.deref(comp);
} }
params.deinit(); params.deinit();
}; };
const is_var_args = false;
{ {
var it = fn_proto.params.iterator(0); var it = fn_proto.params.iterator(0);
while (it.next()) |param_node_ptr| { while (it.next()) |param_node_ptr| {
@ -1219,8 +1260,29 @@ async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.Fn
}); });
} }
} }
const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args);
const key = Type.Fn.Key{
.alignment = null,
.data = Type.Fn.Key.Data{
.Normal = Type.Fn.Key.Normal{
.return_type = return_type,
.params = params.toOwnedSlice(),
.is_var_args = false, // TODO
.cc = Type.Fn.CallingConvention.Auto, // TODO
},
},
};
params_consumed = true; params_consumed = true;
var key_consumed = false;
defer if (!key_consumed) {
for (key.data.Normal.params) |param| {
param.typ.base.deref(comp);
}
comp.gpa().free(key.data.Normal.params);
};
const fn_type = try await (async Type.Fn.get(comp, key) catch unreachable);
key_consumed = true;
errdefer fn_type.base.base.deref(comp); errdefer fn_type.base.base.deref(comp);
return fn_type; return fn_type;

View File

@ -10,8 +10,10 @@ const assert = std.debug.assert;
const Token = std.zig.Token; const Token = std.zig.Token;
const Span = @import("errmsg.zig").Span; const Span = @import("errmsg.zig").Span;
const llvm = @import("llvm.zig"); const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile; const codegen = @import("codegen.zig");
const ObjectFile = codegen.ObjectFile;
const Decl = @import("decl.zig").Decl; const Decl = @import("decl.zig").Decl;
const mem = std.mem;
pub const LVal = enum { pub const LVal = enum {
None, None,
@ -122,6 +124,8 @@ pub const Inst = struct {
Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira), Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira),
Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira), Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira),
Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable), Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable),
Id.VarPtr => return await (async @fieldParentPtr(VarPtr, "base", base).analyze(ira) catch unreachable),
Id.LoadPtr => return await (async @fieldParentPtr(LoadPtr, "base", base).analyze(ira) catch unreachable),
} }
} }
@ -130,6 +134,8 @@ pub const Inst = struct {
Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val), Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val), Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val), Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val),
Id.VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val),
Id.LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val),
Id.DeclRef => unreachable, Id.DeclRef => unreachable,
Id.PtrType => unreachable, Id.PtrType => unreachable,
Id.Ref => @panic("TODO"), Id.Ref => @panic("TODO"),
@ -248,6 +254,8 @@ pub const Inst = struct {
Call, Call,
DeclRef, DeclRef,
PtrType, PtrType,
VarPtr,
LoadPtr,
}; };
pub const Call = struct { pub const Call = struct {
@ -281,11 +289,13 @@ pub const Inst = struct {
return error.SemanticAnalysisFailed; return error.SemanticAnalysisFailed;
}; };
if (fn_type.params.len != self.params.args.len) { const fn_type_param_count = fn_type.paramCount();
if (fn_type_param_count != self.params.args.len) {
try ira.addCompileError( try ira.addCompileError(
self.base.span, self.base.span,
"expected {} arguments, found {}", "expected {} arguments, found {}",
fn_type.params.len, fn_type_param_count,
self.params.args.len, self.params.args.len,
); );
return error.SemanticAnalysisFailed; return error.SemanticAnalysisFailed;
@ -299,7 +309,7 @@ pub const Inst = struct {
.fn_ref = fn_ref, .fn_ref = fn_ref,
.args = args, .args = args,
}); });
new_inst.val = IrVal{ .KnownType = fn_type.return_type }; new_inst.val = IrVal{ .KnownType = fn_type.key.data.Normal.return_type };
return new_inst; return new_inst;
} }
@ -489,6 +499,133 @@ pub const Inst = struct {
} }
}; };
pub const VarPtr = struct {
base: Inst,
params: Params,
const Params = struct {
var_scope: *Scope.Var,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const VarPtr) void {
std.debug.warn("{}", inst.params.var_scope.name);
}
pub fn hasSideEffects(inst: *const VarPtr) bool {
return false;
}
pub async fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst {
switch (self.params.var_scope.data) {
Scope.Var.Data.Const => @panic("TODO"),
Scope.Var.Data.Param => |param| {
const new_inst = try ira.irb.build(
Inst.VarPtr,
self.base.scope,
self.base.span,
Inst.VarPtr.Params{ .var_scope = self.params.var_scope },
);
const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
.child_type = param.typ,
.mut = Type.Pointer.Mut.Const,
.vol = Type.Pointer.Vol.Non,
.size = Type.Pointer.Size.One,
.alignment = Type.Pointer.Align.Abi,
}) catch unreachable);
new_inst.val = IrVal{ .KnownType = &ptr_type.base };
return new_inst;
},
}
}
pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) llvm.ValueRef {
switch (self.params.var_scope.data) {
Scope.Var.Data.Const => unreachable, // turned into Inst.Const in analyze pass
Scope.Var.Data.Param => |param| return param.llvm_value,
}
}
};
pub const LoadPtr = struct {
base: Inst,
params: Params,
const Params = struct {
target: *Inst,
};
const ir_val_init = IrVal.Init.Unknown;
pub fn dump(inst: *const LoadPtr) void {}
pub fn hasSideEffects(inst: *const LoadPtr) bool {
return false;
}
pub async fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
const target_type = target.getKnownType();
if (target_type.id != Type.Id.Pointer) {
try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", target_type.name);
return error.SemanticAnalysisFailed;
}
const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type);
// if (instr_is_comptime(ptr)) {
// if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst ||
// ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
// {
// ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value);
// if (pointee->special != ConstValSpecialRuntime) {
// IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope,
// source_instruction->source_node, child_type);
// copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst);
// result->value.type = child_type;
// return result;
// }
// }
// }
const new_inst = try ira.irb.build(
Inst.LoadPtr,
self.base.scope,
self.base.span,
Inst.LoadPtr.Params{ .target = target },
);
new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type };
return new_inst;
}
pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
const child_type = self.base.getKnownType();
if (!child_type.hasBits()) {
return null;
}
const ptr = self.params.target.llvm_value.?;
const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?;
return try codegen.getHandleValue(ofile, ptr, ptr_type);
//uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count;
//if (unaligned_bit_count == 0)
// return get_handle_value(g, ptr, child_type, ptr_type);
//bool big_endian = g->is_big_endian;
//assert(!handle_is_ptr(child_type));
//LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, "");
//uint32_t bit_offset = ptr_type->data.pointer.bit_offset;
//uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
//uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset;
//LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
//LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, "");
//return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, "");
}
};
pub const PtrType = struct { pub const PtrType = struct {
base: Inst, base: Inst,
params: Params, params: Params,
@ -1158,6 +1295,7 @@ pub const Builder = struct {
Scope.Id.Block, Scope.Id.Block,
Scope.Id.Defer, Scope.Id.Defer,
Scope.Id.DeferExpr, Scope.Id.DeferExpr,
Scope.Id.Var,
=> scope = scope.parent.?, => scope = scope.parent.?,
} }
} }
@ -1259,8 +1397,8 @@ pub const Builder = struct {
var child_scope = outer_block_scope; var child_scope = outer_block_scope;
if (parent_scope.findFnDef()) |fndef_scope| { if (parent_scope.findFnDef()) |fndef_scope| {
if (fndef_scope.fn_val.child_scope == parent_scope) { if (fndef_scope.fn_val.?.block_scope == null) {
fndef_scope.fn_val.block_scope = block_scope; fndef_scope.fn_val.?.block_scope = block_scope;
} }
} }
@ -1490,20 +1628,23 @@ pub const Builder = struct {
error.OutOfMemory => return error.OutOfMemory, error.OutOfMemory => return error.OutOfMemory,
} }
//VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name); switch (await (async irb.findIdent(scope, name) catch unreachable)) {
//if (var) { Ident.Decl => |decl| {
// IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var);
// if (lval == LValPtr)
// return var_ptr;
// else
// return ir_build_load_ptr(irb, scope, node, var_ptr);
//}
if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| {
return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{ return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
.decl = decl, .decl = decl,
.lval = lval, .lval = lval,
}); });
},
Ident.VarScope => |var_scope| {
const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope });
switch (lval) {
LVal.Ptr => return var_ptr,
LVal.None => {
return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr });
},
}
},
Ident.NotFound => {},
} }
//if (node->owner->any_imports_failed) { //if (node->owner->any_imports_failed) {
@ -1544,6 +1685,7 @@ pub const Builder = struct {
Scope.Id.Block, Scope.Id.Block,
Scope.Id.Decls, Scope.Id.Decls,
Scope.Id.Root, Scope.Id.Root,
Scope.Id.Var,
=> scope = scope.parent orelse break, => scope = scope.parent orelse break,
Scope.Id.DeferExpr => unreachable, Scope.Id.DeferExpr => unreachable,
@ -1594,6 +1736,7 @@ pub const Builder = struct {
Scope.Id.CompTime, Scope.Id.CompTime,
Scope.Id.Block, Scope.Id.Block,
Scope.Id.Var,
=> scope = scope.parent orelse return is_noreturn, => scope = scope.parent orelse return is_noreturn,
Scope.Id.DeferExpr => unreachable, Scope.Id.DeferExpr => unreachable,
@ -1672,8 +1815,10 @@ pub const Builder = struct {
Type.Pointer.Size, Type.Pointer.Size,
LVal, LVal,
*Decl, *Decl,
*Scope.Var,
=> {}, => {},
// it's ok to add more types here, just make sure any instructions are ref'd appropriately // it's ok to add more types here, just make sure that
// any instructions and basic blocks are ref'd appropriately
else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)), else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)),
} }
} }
@ -1771,18 +1916,30 @@ pub const Builder = struct {
//// the above blocks are rendered by ir_gen after the rest of codegen //// the above blocks are rendered by ir_gen after the rest of codegen
} }
async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl { const Ident = union(enum) {
NotFound,
Decl: *Decl,
VarScope: *Scope.Var,
};
async fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident {
var s = scope; var s = scope;
while (true) { while (true) {
switch (s.id) { switch (s.id) {
Scope.Id.Root => return Ident.NotFound,
Scope.Id.Decls => { Scope.Id.Decls => {
const decls = @fieldParentPtr(Scope.Decls, "base", s); const decls = @fieldParentPtr(Scope.Decls, "base", s);
const table = await (async decls.getTableReadOnly() catch unreachable); const table = await (async decls.getTableReadOnly() catch unreachable);
if (table.get(name)) |entry| { if (table.get(name)) |entry| {
return entry.value; return Ident{ .Decl = entry.value };
}
},
Scope.Id.Var => {
const var_scope = @fieldParentPtr(Scope.Var, "base", s);
if (mem.eql(u8, var_scope.name, name)) {
return Ident{ .VarScope = var_scope };
} }
}, },
Scope.Id.Root => return null,
else => {}, else => {},
} }
s = s.parent.?; s = s.parent.?;

View File

@ -80,6 +80,12 @@ pub async fn link(comp: *Compilation) !void {
const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat()); const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat());
const args_slice = ctx.args.toSlice(); const args_slice = ctx.args.toSlice();
{
// LLD is not thread-safe, so we grab a global lock.
const held = await (async comp.event_loop_local.lld_lock.acquire() catch unreachable);
defer held.release();
// Not evented I/O. LLD does its own multithreading internally. // Not evented I/O. LLD does its own multithreading internally.
if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) { if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) {
if (!ctx.link_msg.isNull()) { if (!ctx.link_msg.isNull()) {
@ -90,6 +96,7 @@ pub async fn link(comp: *Compilation) !void {
} }
return error.LinkFailed; return error.LinkFailed;
} }
}
} }
extern fn ZigLLDLink( extern fn ZigLLDLink(
@ -672,7 +679,13 @@ const DarwinPlatform = struct {
}; };
var had_extra: bool = undefined; var had_extra: bool = undefined;
try darwinGetReleaseVersion(ver_str, &result.major, &result.minor, &result.micro, &had_extra,); try darwinGetReleaseVersion(
ver_str,
&result.major,
&result.minor,
&result.micro,
&had_extra,
);
if (had_extra or result.major != 10 or result.minor >= 100 or result.micro >= 100) { if (had_extra or result.major != 10 or result.minor >= 100 or result.micro >= 100) {
return error.InvalidDarwinVersionString; return error.InvalidDarwinVersionString;
} }
@ -713,7 +726,7 @@ fn darwinGetReleaseVersion(str: []const u8, major: *u32, minor: *u32, micro: *u3
return error.InvalidDarwinVersionString; return error.InvalidDarwinVersionString;
var start_pos: usize = 0; var start_pos: usize = 0;
for ([]*u32{major, minor, micro}) |v| { for ([]*u32{ major, minor, micro }) |v| {
const dot_pos = mem.indexOfScalarPos(u8, str, start_pos, '.'); const dot_pos = mem.indexOfScalarPos(u8, str, start_pos, '.');
const end_pos = dot_pos orelse str.len; const end_pos = dot_pos orelse str.len;
v.* = std.fmt.parseUnsigned(u32, str[start_pos..end_pos], 10) catch return error.InvalidDarwinVersionString; v.* = std.fmt.parseUnsigned(u32, str[start_pos..end_pos], 10) catch return error.InvalidDarwinVersionString;

View File

@ -30,6 +30,7 @@ pub const AddGlobal = c.LLVMAddGlobal;
pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag; pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag;
pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag; pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag;
pub const ArrayType = c.LLVMArrayType; pub const ArrayType = c.LLVMArrayType;
pub const BuildLoad = c.LLVMBuildLoad;
pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation; pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
pub const ConstAllOnes = c.LLVMConstAllOnes; pub const ConstAllOnes = c.LLVMConstAllOnes;
pub const ConstArray = c.LLVMConstArray; pub const ConstArray = c.LLVMConstArray;
@ -95,13 +96,25 @@ pub const SetInitializer = c.LLVMSetInitializer;
pub const SetLinkage = c.LLVMSetLinkage; pub const SetLinkage = c.LLVMSetLinkage;
pub const SetTarget = c.LLVMSetTarget; pub const SetTarget = c.LLVMSetTarget;
pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr; pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr;
pub const SetVolatile = c.LLVMSetVolatile;
pub const StructTypeInContext = c.LLVMStructTypeInContext; pub const StructTypeInContext = c.LLVMStructTypeInContext;
pub const TokenTypeInContext = c.LLVMTokenTypeInContext; pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
pub const TypeOf = c.LLVMTypeOf;
pub const VoidTypeInContext = c.LLVMVoidTypeInContext; pub const VoidTypeInContext = c.LLVMVoidTypeInContext;
pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext; pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext; pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
pub const GetElementType = LLVMGetElementType;
extern fn LLVMGetElementType(Ty: TypeRef) TypeRef;
pub const TypeOf = LLVMTypeOf;
extern fn LLVMTypeOf(Val: ValueRef) TypeRef;
pub const BuildStore = LLVMBuildStore;
extern fn LLVMBuildStore(arg0: BuilderRef, Val: ValueRef, Ptr: ValueRef) ?ValueRef;
pub const BuildAlloca = LLVMBuildAlloca;
extern fn LLVMBuildAlloca(arg0: BuilderRef, Ty: TypeRef, Name: ?[*]const u8) ?ValueRef;
pub const ConstInBoundsGEP = LLVMConstInBoundsGEP; pub const ConstInBoundsGEP = LLVMConstInBoundsGEP;
pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef; pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef;

View File

@ -6,23 +6,26 @@ const Compilation = @import("compilation.zig").Compilation;
const mem = std.mem; const mem = std.mem;
const ast = std.zig.ast; const ast = std.zig.ast;
const Value = @import("value.zig").Value; const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const ir = @import("ir.zig"); const ir = @import("ir.zig");
const Span = @import("errmsg.zig").Span; const Span = @import("errmsg.zig").Span;
const assert = std.debug.assert; const assert = std.debug.assert;
const event = std.event; const event = std.event;
const llvm = @import("llvm.zig");
pub const Scope = struct { pub const Scope = struct {
id: Id, id: Id,
parent: ?*Scope, parent: ?*Scope,
ref_count: usize, ref_count: std.atomic.Int(usize),
/// Thread-safe
pub fn ref(base: *Scope) void { pub fn ref(base: *Scope) void {
base.ref_count += 1; _ = base.ref_count.incr();
} }
/// Thread-safe
pub fn deref(base: *Scope, comp: *Compilation) void { pub fn deref(base: *Scope, comp: *Compilation) void {
base.ref_count -= 1; if (base.ref_count.decr() == 1) {
if (base.ref_count == 0) {
if (base.parent) |parent| parent.deref(comp); if (base.parent) |parent| parent.deref(comp);
switch (base.id) { switch (base.id) {
Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp), Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp),
@ -32,6 +35,7 @@ pub const Scope = struct {
Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp), Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp),
Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp), Id.Defer => @fieldParentPtr(Defer, "base", base).destroy(comp),
Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp), Id.DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp),
Id.Var => @fieldParentPtr(Var, "base", base).destroy(comp),
} }
} }
} }
@ -49,15 +53,15 @@ pub const Scope = struct {
var scope = base; var scope = base;
while (true) { while (true) {
switch (scope.id) { switch (scope.id) {
Id.FnDef => return @fieldParentPtr(FnDef, "base", base), Id.FnDef => return @fieldParentPtr(FnDef, "base", scope),
Id.Decls => return null, Id.Root, Id.Decls => return null,
Id.Block, Id.Block,
Id.Defer, Id.Defer,
Id.DeferExpr, Id.DeferExpr,
Id.CompTime, Id.CompTime,
Id.Root, Id.Var,
=> scope = scope.parent orelse return null, => scope = scope.parent.?,
} }
} }
} }
@ -66,7 +70,7 @@ pub const Scope = struct {
var scope = base; var scope = base;
while (true) { while (true) {
switch (scope.id) { switch (scope.id) {
Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base), Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope),
Id.FnDef, Id.FnDef,
Id.Decls, Id.Decls,
@ -76,11 +80,21 @@ pub const Scope = struct {
Id.Defer, Id.Defer,
Id.CompTime, Id.CompTime,
Id.Root, Id.Root,
Id.Var,
=> scope = scope.parent orelse return null, => scope = scope.parent orelse return null,
} }
} }
} }
fn init(base: *Scope, id: Id, parent: *Scope) void {
base.* = Scope{
.id = id,
.parent = parent,
.ref_count = std.atomic.Int(usize).init(1),
};
parent.ref();
}
pub const Id = enum { pub const Id = enum {
Root, Root,
Decls, Decls,
@ -89,6 +103,7 @@ pub const Scope = struct {
CompTime, CompTime,
Defer, Defer,
DeferExpr, DeferExpr,
Var,
}; };
pub const Root = struct { pub const Root = struct {
@ -100,16 +115,16 @@ pub const Scope = struct {
/// Takes ownership of realpath /// Takes ownership of realpath
/// Takes ownership of tree, will deinit and destroy when done. /// Takes ownership of tree, will deinit and destroy when done.
pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root { pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root {
const self = try comp.gpa().create(Root{ const self = try comp.gpa().createOne(Root);
self.* = Root{
.base = Scope{ .base = Scope{
.id = Id.Root, .id = Id.Root,
.parent = null, .parent = null,
.ref_count = 1, .ref_count = std.atomic.Int(usize).init(1),
}, },
.tree = tree, .tree = tree,
.realpath = realpath, .realpath = realpath,
}); };
errdefer comp.gpa().destroy(self);
return self; return self;
} }
@ -137,16 +152,13 @@ pub const Scope = struct {
/// Creates a Decls scope with 1 reference /// Creates a Decls scope with 1 reference
pub fn create(comp: *Compilation, parent: *Scope) !*Decls { pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
const self = try comp.gpa().create(Decls{ const self = try comp.gpa().createOne(Decls);
.base = Scope{ self.* = Decls{
.id = Id.Decls, .base = undefined,
.parent = parent,
.ref_count = 1,
},
.table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())), .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
.name_future = event.Future(void).init(comp.loop), .name_future = event.Future(void).init(comp.loop),
}); };
parent.ref(); self.base.init(Id.Decls, parent);
return self; return self;
} }
@ -199,21 +211,16 @@ pub const Scope = struct {
/// Creates a Block scope with 1 reference /// Creates a Block scope with 1 reference
pub fn create(comp: *Compilation, parent: *Scope) !*Block { pub fn create(comp: *Compilation, parent: *Scope) !*Block {
const self = try comp.gpa().create(Block{ const self = try comp.gpa().createOne(Block);
.base = Scope{ self.* = Block{
.id = Id.Block, .base = undefined,
.parent = parent,
.ref_count = 1,
},
.incoming_values = undefined, .incoming_values = undefined,
.incoming_blocks = undefined, .incoming_blocks = undefined,
.end_block = undefined, .end_block = undefined,
.is_comptime = undefined, .is_comptime = undefined,
.safety = Safety.Auto, .safety = Safety.Auto,
}); };
errdefer comp.gpa().destroy(self); self.base.init(Id.Block, parent);
parent.ref();
return self; return self;
} }
@ -226,22 +233,17 @@ pub const Scope = struct {
base: Scope, base: Scope,
/// This reference is not counted so that the scope can get destroyed with the function /// This reference is not counted so that the scope can get destroyed with the function
fn_val: *Value.Fn, fn_val: ?*Value.Fn,
/// Creates a FnDef scope with 1 reference /// Creates a FnDef scope with 1 reference
/// Must set the fn_val later /// Must set the fn_val later
pub fn create(comp: *Compilation, parent: *Scope) !*FnDef { pub fn create(comp: *Compilation, parent: *Scope) !*FnDef {
const self = try comp.gpa().create(FnDef{ const self = try comp.gpa().createOne(FnDef);
.base = Scope{ self.* = FnDef{
.id = Id.FnDef, .base = undefined,
.parent = parent, .fn_val = null,
.ref_count = 1, };
}, self.base.init(Id.FnDef, parent);
.fn_val = undefined,
});
parent.ref();
return self; return self;
} }
@ -255,15 +257,9 @@ pub const Scope = struct {
/// Creates a CompTime scope with 1 reference /// Creates a CompTime scope with 1 reference
pub fn create(comp: *Compilation, parent: *Scope) !*CompTime { pub fn create(comp: *Compilation, parent: *Scope) !*CompTime {
const self = try comp.gpa().create(CompTime{ const self = try comp.gpa().createOne(CompTime);
.base = Scope{ self.* = CompTime{ .base = undefined };
.id = Id.CompTime, self.base.init(Id.CompTime, parent);
.parent = parent,
.ref_count = 1,
},
});
parent.ref();
return self; return self;
} }
@ -289,20 +285,14 @@ pub const Scope = struct {
kind: Kind, kind: Kind,
defer_expr_scope: *DeferExpr, defer_expr_scope: *DeferExpr,
) !*Defer { ) !*Defer {
const self = try comp.gpa().create(Defer{ const self = try comp.gpa().createOne(Defer);
.base = Scope{ self.* = Defer{
.id = Id.Defer, .base = undefined,
.parent = parent,
.ref_count = 1,
},
.defer_expr_scope = defer_expr_scope, .defer_expr_scope = defer_expr_scope,
.kind = kind, .kind = kind,
}); };
errdefer comp.gpa().destroy(self); self.base.init(Id.Defer, parent);
defer_expr_scope.base.ref(); defer_expr_scope.base.ref();
parent.ref();
return self; return self;
} }
@ -319,18 +309,13 @@ pub const Scope = struct {
/// Creates a DeferExpr scope with 1 reference /// Creates a DeferExpr scope with 1 reference
pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr { pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr {
const self = try comp.gpa().create(DeferExpr{ const self = try comp.gpa().createOne(DeferExpr);
.base = Scope{ self.* = DeferExpr{
.id = Id.DeferExpr, .base = undefined,
.parent = parent,
.ref_count = 1,
},
.expr_node = expr_node, .expr_node = expr_node,
.reported_err = false, .reported_err = false,
}); };
errdefer comp.gpa().destroy(self); self.base.init(Id.DeferExpr, parent);
parent.ref();
return self; return self;
} }
@ -338,4 +323,74 @@ pub const Scope = struct {
comp.gpa().destroy(self); comp.gpa().destroy(self);
} }
}; };
pub const Var = struct {
base: Scope,
name: []const u8,
src_node: *ast.Node,
data: Data,
pub const Data = union(enum) {
Param: Param,
Const: *Value,
};
pub const Param = struct {
index: usize,
typ: *Type,
llvm_value: llvm.ValueRef,
};
pub fn createParam(
comp: *Compilation,
parent: *Scope,
name: []const u8,
src_node: *ast.Node,
param_index: usize,
param_type: *Type,
) !*Var {
const self = try create(comp, parent, name, src_node);
self.data = Data{
.Param = Param{
.index = param_index,
.typ = param_type,
.llvm_value = undefined,
},
};
return self;
}
pub fn createConst(
comp: *Compilation,
parent: *Scope,
name: []const u8,
src_node: *ast.Node,
value: *Value,
) !*Var {
const self = try create(comp, parent, name, src_node);
self.data = Data{ .Const = value };
value.ref();
return self;
}
fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var {
const self = try comp.gpa().createOne(Var);
self.* = Var{
.base = undefined,
.name = name,
.src_node = src_node,
.data = undefined,
};
self.base.init(Id.Var, parent);
return self;
}
pub fn destroy(self: *Var, comp: *Compilation) void {
switch (self.data) {
Data.Param => {},
Data.Const => |value| value.deref(comp),
}
comp.gpa().destroy(self);
}
};
}; };

View File

@ -141,9 +141,13 @@ pub const Type = struct {
Id.Promise, Id.Promise,
=> return true, => return true,
Id.Pointer => {
const ptr_type = @fieldParentPtr(Pointer, "base", base);
return ptr_type.key.child_type.hasBits();
},
Id.ErrorSet => @panic("TODO"), Id.ErrorSet => @panic("TODO"),
Id.Enum => @panic("TODO"), Id.Enum => @panic("TODO"),
Id.Pointer => @panic("TODO"),
Id.Struct => @panic("TODO"), Id.Struct => @panic("TODO"),
Id.Array => @panic("TODO"), Id.Array => @panic("TODO"),
Id.Optional => @panic("TODO"), Id.Optional => @panic("TODO"),
@ -221,57 +225,294 @@ pub const Type = struct {
pub const Fn = struct { pub const Fn = struct {
base: Type, base: Type,
return_type: *Type, key: Key,
non_key: NonKey,
garbage_node: std.atomic.Stack(*Fn).Node,
pub const Kind = enum {
Normal,
Generic,
};
pub const NonKey = union {
Normal: Normal,
Generic: void,
pub const Normal = struct {
variable_list: std.ArrayList(*Scope.Var),
};
};
pub const Key = struct {
data: Data,
alignment: ?u32,
pub const Data = union(Kind) {
Generic: Generic,
Normal: Normal,
};
pub const Normal = struct {
params: []Param, params: []Param,
return_type: *Type,
is_var_args: bool, is_var_args: bool,
cc: CallingConvention,
};
pub const Generic = struct {
param_count: usize,
cc: CC,
pub const CC = union(CallingConvention) {
Auto,
C,
Cold,
Naked,
Stdcall,
Async: *Type, // allocator type
};
};
pub fn hash(self: *const Key) u32 {
var result: u32 = 0;
result +%= hashAny(self.alignment, 0);
switch (self.data) {
Kind.Generic => |generic| {
result +%= hashAny(generic.param_count, 1);
switch (generic.cc) {
CallingConvention.Async => |allocator_type| result +%= hashAny(allocator_type, 2),
else => result +%= hashAny(CallingConvention(generic.cc), 3),
}
},
Kind.Normal => |normal| {
result +%= hashAny(normal.return_type, 4);
result +%= hashAny(normal.is_var_args, 5);
result +%= hashAny(normal.cc, 6);
for (normal.params) |param| {
result +%= hashAny(param.is_noalias, 7);
result +%= hashAny(param.typ, 8);
}
},
}
return result;
}
pub fn eql(self: *const Key, other: *const Key) bool {
if ((self.alignment == null) != (other.alignment == null)) return false;
if (self.alignment) |self_align| {
if (self_align != other.alignment.?) return false;
}
if (@TagType(Data)(self.data) != @TagType(Data)(other.data)) return false;
switch (self.data) {
Kind.Generic => |*self_generic| {
const other_generic = &other.data.Generic;
if (self_generic.param_count != other_generic.param_count) return false;
if (CallingConvention(self_generic.cc) != CallingConvention(other_generic.cc)) return false;
switch (self_generic.cc) {
CallingConvention.Async => |self_allocator_type| {
const other_allocator_type = other_generic.cc.Async;
if (self_allocator_type != other_allocator_type) return false;
},
else => {},
}
},
Kind.Normal => |*self_normal| {
const other_normal = &other.data.Normal;
if (self_normal.cc != other_normal.cc) return false;
if (self_normal.is_var_args != other_normal.is_var_args) return false;
if (self_normal.return_type != other_normal.return_type) return false;
for (self_normal.params) |*self_param, i| {
const other_param = &other_normal.params[i];
if (self_param.is_noalias != other_param.is_noalias) return false;
if (self_param.typ != other_param.typ) return false;
}
},
}
return true;
}
pub fn deref(key: Key, comp: *Compilation) void {
switch (key.data) {
Kind.Generic => |generic| {
switch (generic.cc) {
CallingConvention.Async => |allocator_type| allocator_type.base.deref(comp),
else => {},
}
},
Kind.Normal => |normal| {
normal.return_type.base.deref(comp);
for (normal.params) |param| {
param.typ.base.deref(comp);
}
},
}
}
pub fn ref(key: Key) void {
switch (key.data) {
Kind.Generic => |generic| {
switch (generic.cc) {
CallingConvention.Async => |allocator_type| allocator_type.base.ref(),
else => {},
}
},
Kind.Normal => |normal| {
normal.return_type.base.ref();
for (normal.params) |param| {
param.typ.base.ref();
}
},
}
}
};
pub const CallingConvention = enum {
Auto,
C,
Cold,
Naked,
Stdcall,
Async,
};
pub const Param = struct { pub const Param = struct {
is_noalias: bool, is_noalias: bool,
typ: *Type, typ: *Type,
}; };
pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn { fn ccFnTypeStr(cc: CallingConvention) []const u8 {
const result = try comp.gpa().create(Fn{ return switch (cc) {
.base = undefined, CallingConvention.Auto => "",
.return_type = return_type, CallingConvention.C => "extern ",
.params = params, CallingConvention.Cold => "coldcc ",
.is_var_args = is_var_args, CallingConvention.Naked => "nakedcc ",
}); CallingConvention.Stdcall => "stdcallcc ",
errdefer comp.gpa().destroy(result); CallingConvention.Async => unreachable,
};
result.base.init(comp, Id.Fn, "TODO fn type name");
result.return_type.base.ref();
for (result.params) |param| {
param.typ.base.ref();
} }
return result;
pub fn paramCount(self: *Fn) usize {
return switch (self.key.data) {
Kind.Generic => |generic| generic.param_count,
Kind.Normal => |normal| normal.params.len,
};
}
/// takes ownership of key.Normal.params on success
pub async fn get(comp: *Compilation, key: Key) !*Fn {
{
const held = await (async comp.fn_type_table.acquire() catch unreachable);
defer held.release();
if (held.value.get(&key)) |entry| {
entry.value.base.base.ref();
return entry.value;
}
}
key.ref();
errdefer key.deref(comp);
const self = try comp.gpa().createOne(Fn);
self.* = Fn{
.base = undefined,
.key = key,
.non_key = undefined,
.garbage_node = undefined,
};
errdefer comp.gpa().destroy(self);
var name_buf = try std.Buffer.initSize(comp.gpa(), 0);
defer name_buf.deinit();
const name_stream = &std.io.BufferOutStream.init(&name_buf).stream;
switch (key.data) {
Kind.Generic => |generic| {
self.non_key = NonKey{ .Generic = {} };
switch (generic.cc) {
CallingConvention.Async => |async_allocator_type| {
try name_stream.print("async<{}> ", async_allocator_type.name);
},
else => {
const cc_str = ccFnTypeStr(generic.cc);
try name_stream.write(cc_str);
},
}
try name_stream.write("fn(");
var param_i: usize = 0;
while (param_i < generic.param_count) : (param_i += 1) {
const arg = if (param_i == 0) "var" else ", var";
try name_stream.write(arg);
}
try name_stream.write(")");
if (key.alignment) |alignment| {
try name_stream.print(" align<{}>", alignment);
}
try name_stream.write(" var");
},
Kind.Normal => |normal| {
self.non_key = NonKey{
.Normal = NonKey.Normal{ .variable_list = std.ArrayList(*Scope.Var).init(comp.gpa()) },
};
const cc_str = ccFnTypeStr(normal.cc);
try name_stream.print("{}fn(", cc_str);
for (normal.params) |param, i| {
if (i != 0) try name_stream.write(", ");
if (param.is_noalias) try name_stream.write("noalias ");
try name_stream.write(param.typ.name);
}
if (normal.is_var_args) {
if (normal.params.len != 0) try name_stream.write(", ");
try name_stream.write("...");
}
try name_stream.write(")");
if (key.alignment) |alignment| {
try name_stream.print(" align<{}>", alignment);
}
try name_stream.print(" {}", normal.return_type.name);
},
}
self.base.init(comp, Id.Fn, name_buf.toOwnedSlice());
{
const held = await (async comp.fn_type_table.acquire() catch unreachable);
defer held.release();
_ = try held.value.put(&self.key, self);
}
return self;
} }
pub fn destroy(self: *Fn, comp: *Compilation) void { pub fn destroy(self: *Fn, comp: *Compilation) void {
self.return_type.base.deref(comp); self.key.deref(comp);
for (self.params) |param| { switch (self.key.data) {
param.typ.base.deref(comp); Kind.Generic => {},
Kind.Normal => {
self.non_key.Normal.variable_list.deinit();
},
} }
comp.gpa().destroy(self); comp.gpa().destroy(self);
} }
pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef { pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const llvm_return_type = switch (self.return_type.id) { const normal = &self.key.data.Normal;
const llvm_return_type = switch (normal.return_type.id) {
Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory, Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory,
else => try self.return_type.getLlvmType(allocator, llvm_context), else => try normal.return_type.getLlvmType(allocator, llvm_context),
}; };
const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len); const llvm_param_types = try allocator.alloc(llvm.TypeRef, normal.params.len);
defer allocator.free(llvm_param_types); defer allocator.free(llvm_param_types);
for (llvm_param_types) |*llvm_param_type, i| { for (llvm_param_types) |*llvm_param_type, i| {
llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context); llvm_param_type.* = try normal.params[i].typ.getLlvmType(allocator, llvm_context);
} }
return llvm.FunctionType( return llvm.FunctionType(
llvm_return_type, llvm_return_type,
llvm_param_types.ptr, llvm_param_types.ptr,
@intCast(c_uint, llvm_param_types.len), @intCast(c_uint, llvm_param_types.len),
@boolToInt(self.is_var_args), @boolToInt(normal.is_var_args),
) orelse error.OutOfMemory; ) orelse error.OutOfMemory;
} }
}; };
@ -347,8 +588,10 @@ pub const Type = struct {
is_signed: bool, is_signed: bool,
pub fn hash(self: *const Key) u32 { pub fn hash(self: *const Key) u32 {
const rands = [2]u32{ 0xa4ba6498, 0x75fc5af7 }; var result: u32 = 0;
return rands[@boolToInt(self.is_signed)] *% self.bit_count; result +%= hashAny(self.is_signed, 0);
result +%= hashAny(self.bit_count, 1);
return result;
} }
pub fn eql(self: *const Key, other: *const Key) bool { pub fn eql(self: *const Key, other: *const Key) bool {
@ -443,15 +686,16 @@ pub const Type = struct {
alignment: Align, alignment: Align,
pub fn hash(self: *const Key) u32 { pub fn hash(self: *const Key) u32 {
const align_hash = switch (self.alignment) { var result: u32 = 0;
result +%= switch (self.alignment) {
Align.Abi => 0xf201c090, Align.Abi => 0xf201c090,
Align.Override => |x| x, Align.Override => |x| hashAny(x, 0),
}; };
return hash_usize(@ptrToInt(self.child_type)) *% result +%= hashAny(self.child_type, 1);
hash_enum(self.mut) *% result +%= hashAny(self.mut, 2);
hash_enum(self.vol) *% result +%= hashAny(self.vol, 3);
hash_enum(self.size) *% result +%= hashAny(self.size, 4);
align_hash; return result;
} }
pub fn eql(self: *const Key, other: *const Key) bool { pub fn eql(self: *const Key, other: *const Key) bool {
@ -605,7 +849,10 @@ pub const Type = struct {
len: usize, len: usize,
pub fn hash(self: *const Key) u32 { pub fn hash(self: *const Key) u32 {
return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len); var result: u32 = 0;
result +%= hashAny(self.elem_type, 0);
result +%= hashAny(self.len, 1);
return result;
} }
pub fn eql(self: *const Key, other: *const Key) bool { pub fn eql(self: *const Key, other: *const Key) bool {
@ -818,27 +1065,37 @@ pub const Type = struct {
}; };
}; };
fn hash_usize(x: usize) u32 { fn hashAny(x: var, comptime seed: u64) u32 {
return switch (@sizeOf(usize)) { switch (@typeInfo(@typeOf(x))) {
4 => x, builtin.TypeId.Int => |info| {
8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d), comptime var rng = comptime std.rand.DefaultPrng.init(seed);
else => @compileError("implement this hash function"), const unsigned_x = @bitCast(@IntType(false, info.bits), x);
}; if (info.bits <= 32) {
} return u32(unsigned_x) *% comptime rng.random.scalar(u32);
} else {
fn hash_enum(x: var) u32 { return @truncate(u32, unsigned_x *% comptime rng.random.scalar(@typeOf(unsigned_x)));
const rands = []u32{ }
0x85ebf64f, },
0x3fcb3211, builtin.TypeId.Pointer => |info| {
0x240a4e8e, switch (info.size) {
0x40bb0e3c, builtin.TypeInfo.Pointer.Size.One => return hashAny(@ptrToInt(x), seed),
0x78be45af, builtin.TypeInfo.Pointer.Size.Many => @compileError("implement hash function"),
0x1ca98e37, builtin.TypeInfo.Pointer.Size.Slice => @compileError("implement hash function"),
0xec56053a, }
0x906adc48, },
0xd4fe9763, builtin.TypeId.Enum => return hashAny(@enumToInt(x), seed),
0x54c80dac, builtin.TypeId.Bool => {
}; comptime var rng = comptime std.rand.DefaultPrng.init(seed);
comptime assert(@memberCount(@typeOf(x)) < rands.len); const vals = comptime [2]u32{ rng.random.scalar(u32), rng.random.scalar(u32) };
return rands[@enumToInt(x)]; return vals[@boolToInt(x)];
},
builtin.TypeId.Optional => {
if (x) |non_opt| {
return hashAny(non_opt, seed);
} else {
return hashAny(u32(1), seed);
}
},
else => @compileError("implement hash function for " ++ @typeName(@typeOf(x))),
}
} }

View File

@ -60,7 +60,7 @@ pub const Value = struct {
pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) { pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?llvm.ValueRef) {
switch (base.id) { switch (base.id) {
Id.Type => unreachable, Id.Type => unreachable,
Id.Fn => @panic("TODO"), Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile),
Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile), Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
Id.Void => return null, Id.Void => return null,
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile), Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
@ -180,7 +180,7 @@ pub const Value = struct {
child_scope: *Scope, child_scope: *Scope,
/// parent is child_scope /// parent is child_scope
block_scope: *Scope.Block, block_scope: ?*Scope.Block,
/// Path to the object file that contains this function /// Path to the object file that contains this function
containing_object: Buffer, containing_object: Buffer,
@ -205,7 +205,7 @@ pub const Value = struct {
}, },
.fndef_scope = fndef_scope, .fndef_scope = fndef_scope,
.child_scope = &fndef_scope.base, .child_scope = &fndef_scope.base,
.block_scope = undefined, .block_scope = null,
.symbol_name = symbol_name, .symbol_name = symbol_name,
.containing_object = Buffer.initNull(comp.gpa()), .containing_object = Buffer.initNull(comp.gpa()),
.link_set_node = link_set_node, .link_set_node = link_set_node,
@ -231,6 +231,22 @@ pub const Value = struct {
self.symbol_name.deinit(); self.symbol_name.deinit();
comp.gpa().destroy(self); comp.gpa().destroy(self);
} }
/// We know that the function definition will end up in an .o file somewhere.
/// Here, all we have to do is generate a global prototype.
/// TODO cache the prototype per ObjectFile
pub fn getLlvmConst(self: *Fn, ofile: *ObjectFile) !?llvm.ValueRef {
const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
const llvm_fn = llvm.AddFunction(
ofile.module,
self.symbol_name.ptr(),
llvm_fn_type,
) orelse return error.OutOfMemory;
// TODO port more logic from codegen.cpp:fn_llvm_value
return llvm_fn;
}
}; };
pub const Void = struct { pub const Void = struct {

View File

@ -60,7 +60,7 @@ struct IrExecutable {
ZigList<Tld *> tld_list; ZigList<Tld *> tld_list;
IrInstruction *coro_handle; IrInstruction *coro_handle;
IrInstruction *coro_awaiter_field_ptr; // this one is shared and in the promise IrInstruction *atomic_state_field_ptr; // this one is shared and in the promise
IrInstruction *coro_result_ptr_field_ptr; IrInstruction *coro_result_ptr_field_ptr;
IrInstruction *coro_result_field_ptr; IrInstruction *coro_result_field_ptr;
IrInstruction *await_handle_var_ptr; // this one is where we put the one we extracted from the promise IrInstruction *await_handle_var_ptr; // this one is where we put the one we extracted from the promise
@ -898,7 +898,6 @@ struct AstNodeAwaitExpr {
}; };
struct AstNodeSuspend { struct AstNodeSuspend {
Buf *name;
AstNode *block; AstNode *block;
AstNode *promise_symbol; AstNode *promise_symbol;
}; };
@ -1927,7 +1926,6 @@ struct ScopeLoop {
struct ScopeSuspend { struct ScopeSuspend {
Scope base; Scope base;
Buf *name;
IrBasicBlock *resume_block; IrBasicBlock *resume_block;
bool reported_err; bool reported_err;
}; };
@ -3243,7 +3241,7 @@ static const size_t stack_trace_ptr_count = 30;
#define RESULT_FIELD_NAME "result" #define RESULT_FIELD_NAME "result"
#define ASYNC_ALLOC_FIELD_NAME "allocFn" #define ASYNC_ALLOC_FIELD_NAME "allocFn"
#define ASYNC_FREE_FIELD_NAME "freeFn" #define ASYNC_FREE_FIELD_NAME "freeFn"
#define AWAITER_HANDLE_FIELD_NAME "awaiter_handle" #define ATOMIC_STATE_FIELD_NAME "atomic_state"
// these point to data belonging to the awaiter // these point to data belonging to the awaiter
#define ERR_RET_TRACE_PTR_FIELD_NAME "err_ret_trace_ptr" #define ERR_RET_TRACE_PTR_FIELD_NAME "err_ret_trace_ptr"
#define RESULT_PTR_FIELD_NAME "result_ptr" #define RESULT_PTR_FIELD_NAME "result_ptr"

View File

@ -161,7 +161,6 @@ ScopeSuspend *create_suspend_scope(AstNode *node, Scope *parent) {
assert(node->type == NodeTypeSuspend); assert(node->type == NodeTypeSuspend);
ScopeSuspend *scope = allocate<ScopeSuspend>(1); ScopeSuspend *scope = allocate<ScopeSuspend>(1);
init_scope(&scope->base, ScopeIdSuspend, node, parent); init_scope(&scope->base, ScopeIdSuspend, node, parent);
scope->name = node->data.suspend.name;
return scope; return scope;
} }
@ -519,11 +518,11 @@ TypeTableEntry *get_promise_frame_type(CodeGen *g, TypeTableEntry *return_type)
return return_type->promise_frame_parent; return return_type->promise_frame_parent;
} }
TypeTableEntry *awaiter_handle_type = get_optional_type(g, g->builtin_types.entry_promise); TypeTableEntry *atomic_state_type = g->builtin_types.entry_usize;
TypeTableEntry *result_ptr_type = get_pointer_to_type(g, return_type, false); TypeTableEntry *result_ptr_type = get_pointer_to_type(g, return_type, false);
ZigList<const char *> field_names = {}; ZigList<const char *> field_names = {};
field_names.append(AWAITER_HANDLE_FIELD_NAME); field_names.append(ATOMIC_STATE_FIELD_NAME);
field_names.append(RESULT_FIELD_NAME); field_names.append(RESULT_FIELD_NAME);
field_names.append(RESULT_PTR_FIELD_NAME); field_names.append(RESULT_PTR_FIELD_NAME);
if (g->have_err_ret_tracing) { if (g->have_err_ret_tracing) {
@ -533,7 +532,7 @@ TypeTableEntry *get_promise_frame_type(CodeGen *g, TypeTableEntry *return_type)
} }
ZigList<TypeTableEntry *> field_types = {}; ZigList<TypeTableEntry *> field_types = {};
field_types.append(awaiter_handle_type); field_types.append(atomic_state_type);
field_types.append(return_type); field_types.append(return_type);
field_types.append(result_ptr_type); field_types.append(result_ptr_type);
if (g->have_err_ret_tracing) { if (g->have_err_ret_tracing) {
@ -1585,10 +1584,6 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c
case TypeTableEntryIdBlock: case TypeTableEntryIdBlock:
case TypeTableEntryIdBoundFn: case TypeTableEntryIdBoundFn:
case TypeTableEntryIdMetaType: case TypeTableEntryIdMetaType:
add_node_error(g, param_node->data.param_decl.type,
buf_sprintf("parameter of type '%s' must be declared comptime",
buf_ptr(&type_entry->name)));
return g->builtin_types.entry_invalid;
case TypeTableEntryIdVoid: case TypeTableEntryIdVoid:
case TypeTableEntryIdBool: case TypeTableEntryIdBool:
case TypeTableEntryIdInt: case TypeTableEntryIdInt:
@ -1603,6 +1598,13 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c
case TypeTableEntryIdUnion: case TypeTableEntryIdUnion:
case TypeTableEntryIdFn: case TypeTableEntryIdFn:
case TypeTableEntryIdPromise: case TypeTableEntryIdPromise:
type_ensure_zero_bits_known(g, type_entry);
if (type_requires_comptime(type_entry)) {
add_node_error(g, param_node->data.param_decl.type,
buf_sprintf("parameter of type '%s' must be declared comptime",
buf_ptr(&type_entry->name)));
return g->builtin_types.entry_invalid;
}
break; break;
} }
FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index]; FnTypeParamInfo *param_info = &fn_type_id.param_info[fn_type_id.next_param_index];
@ -3938,7 +3940,7 @@ AstNode *get_param_decl_node(FnTableEntry *fn_entry, size_t index) {
return nullptr; return nullptr;
} }
static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, VariableTableEntry **arg_vars) { static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry) {
TypeTableEntry *fn_type = fn_table_entry->type_entry; TypeTableEntry *fn_type = fn_table_entry->type_entry;
assert(!fn_type->data.fn.is_generic); assert(!fn_type->data.fn.is_generic);
FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id;
@ -3976,10 +3978,6 @@ static void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entr
if (fn_type->data.fn.gen_param_info) { if (fn_type->data.fn.gen_param_info) {
var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index; var->gen_arg_index = fn_type->data.fn.gen_param_info[i].gen_index;
} }
if (arg_vars) {
arg_vars[i] = var;
}
} }
} }
@ -4057,7 +4055,7 @@ void analyze_fn_ir(CodeGen *g, FnTableEntry *fn_table_entry, AstNode *return_typ
} }
if (g->verbose_ir) { if (g->verbose_ir) {
fprintf(stderr, "{ // (analyzed)\n"); fprintf(stderr, "fn %s() { // (analyzed)\n", buf_ptr(&fn_table_entry->symbol_name));
ir_print(g, stderr, &fn_table_entry->analyzed_executable, 4); ir_print(g, stderr, &fn_table_entry->analyzed_executable, 4);
fprintf(stderr, "}\n"); fprintf(stderr, "}\n");
} }
@ -4079,7 +4077,7 @@ static void analyze_fn_body(CodeGen *g, FnTableEntry *fn_table_entry) {
if (!fn_table_entry->child_scope) if (!fn_table_entry->child_scope)
fn_table_entry->child_scope = &fn_table_entry->fndef_scope->base; fn_table_entry->child_scope = &fn_table_entry->fndef_scope->base;
define_local_param_variables(g, fn_table_entry, nullptr); define_local_param_variables(g, fn_table_entry);
TypeTableEntry *fn_type = fn_table_entry->type_entry; TypeTableEntry *fn_type = fn_table_entry->type_entry;
assert(!fn_type->data.fn.is_generic); assert(!fn_type->data.fn.is_generic);
@ -5019,9 +5017,10 @@ bool type_requires_comptime(TypeTableEntry *type_entry) {
} else { } else {
return type_requires_comptime(type_entry->data.pointer.child_type); return type_requires_comptime(type_entry->data.pointer.child_type);
} }
case TypeTableEntryIdFn:
return type_entry->data.fn.is_generic;
case TypeTableEntryIdEnum: case TypeTableEntryIdEnum:
case TypeTableEntryIdErrorSet: case TypeTableEntryIdErrorSet:
case TypeTableEntryIdFn:
case TypeTableEntryIdBool: case TypeTableEntryIdBool:
case TypeTableEntryIdInt: case TypeTableEntryIdInt:
case TypeTableEntryIdFloat: case TypeTableEntryIdFloat:
@ -6228,7 +6227,12 @@ uint32_t get_abi_alignment(CodeGen *g, TypeTableEntry *type_entry) {
} else if (type_entry->id == TypeTableEntryIdOpaque) { } else if (type_entry->id == TypeTableEntryIdOpaque) {
return 1; return 1;
} else { } else {
return LLVMABIAlignmentOfType(g->target_data_ref, type_entry->type_ref); uint32_t llvm_alignment = LLVMABIAlignmentOfType(g->target_data_ref, type_entry->type_ref);
// promises have at least alignment 8 so that we can have 3 extra bits when doing atomicrmw
if (type_entry->id == TypeTableEntryIdPromise && llvm_alignment < 8) {
return 8;
}
return llvm_alignment;
} }
} }

View File

@ -3097,20 +3097,47 @@ static IrInstruction *ir_gen_async_return(IrBuilder *irb, Scope *scope, AstNode
return return_inst; return return_inst;
} }
ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value); IrBasicBlock *suspended_block = ir_create_basic_block(irb, scope, "Suspended");
IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, scope, "NotSuspended");
get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise)); IrBasicBlock *store_awaiter_block = ir_create_basic_block(irb, scope, "StoreAwaiter");
// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig IrBasicBlock *check_canceled_block = ir_create_basic_block(irb, scope, "CheckCanceled");
IrInstruction *replacement_value = irb->exec->coro_handle;
IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node, IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr, IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
AtomicRmwOp_xchg, AtomicOrderSeqCst); IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle); IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle); IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_promise);
IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false); IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final, IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
is_comptime);
// the above blocks are rendered by ir_gen after the rest of codegen ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, ptr_mask, nullptr,
AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
ir_build_cond_br(irb, scope, node, is_suspended_bool, suspended_block, not_suspended_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, suspended_block);
ir_build_unreachable(irb, scope, node);
ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
IrInstruction *await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
// if we ever add null checking safety to the ptrtoint instruction, it needs to be disabled here
IrInstruction *have_await_handle = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
ir_build_cond_br(irb, scope, node, have_await_handle, store_awaiter_block, check_canceled_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, store_awaiter_block);
IrInstruction *await_handle = ir_build_int_to_ptr(irb, scope, node, promise_type_val, await_handle_addr);
ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, await_handle);
ir_build_br(irb, scope, node, irb->exec->coro_normal_final, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, check_canceled_block);
IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
return ir_build_cond_br(irb, scope, node, is_canceled_bool, irb->exec->coro_final_cleanup_block, irb->exec->coro_early_final, is_comptime);
} }
static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) { static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
@ -5251,8 +5278,10 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
if (body_result == irb->codegen->invalid_instruction) if (body_result == irb->codegen->invalid_instruction)
return body_result; return body_result;
if (!instr_is_unreachable(body_result)) if (!instr_is_unreachable(body_result)) {
ir_mark_gen(ir_build_check_statement_is_void(irb, payload_scope, node->data.while_expr.body, body_result));
ir_mark_gen(ir_build_br(irb, payload_scope, node, continue_block, is_comptime)); ir_mark_gen(ir_build_br(irb, payload_scope, node, continue_block, is_comptime));
}
if (continue_expr_node) { if (continue_expr_node) {
ir_set_cursor_at_end_and_append_block(irb, continue_block); ir_set_cursor_at_end_and_append_block(irb, continue_block);
@ -5331,8 +5360,10 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
if (body_result == irb->codegen->invalid_instruction) if (body_result == irb->codegen->invalid_instruction)
return body_result; return body_result;
if (!instr_is_unreachable(body_result)) if (!instr_is_unreachable(body_result)) {
ir_mark_gen(ir_build_check_statement_is_void(irb, child_scope, node->data.while_expr.body, body_result));
ir_mark_gen(ir_build_br(irb, child_scope, node, continue_block, is_comptime)); ir_mark_gen(ir_build_br(irb, child_scope, node, continue_block, is_comptime));
}
if (continue_expr_node) { if (continue_expr_node) {
ir_set_cursor_at_end_and_append_block(irb, continue_block); ir_set_cursor_at_end_and_append_block(irb, continue_block);
@ -5392,8 +5423,10 @@ static IrInstruction *ir_gen_while_expr(IrBuilder *irb, Scope *scope, AstNode *n
if (body_result == irb->codegen->invalid_instruction) if (body_result == irb->codegen->invalid_instruction)
return body_result; return body_result;
if (!instr_is_unreachable(body_result)) if (!instr_is_unreachable(body_result)) {
ir_mark_gen(ir_build_check_statement_is_void(irb, scope, node->data.while_expr.body, body_result));
ir_mark_gen(ir_build_br(irb, scope, node, continue_block, is_comptime)); ir_mark_gen(ir_build_br(irb, scope, node, continue_block, is_comptime));
}
if (continue_expr_node) { if (continue_expr_node) {
ir_set_cursor_at_end_and_append_block(irb, continue_block); ir_set_cursor_at_end_and_append_block(irb, continue_block);
@ -6153,15 +6186,6 @@ static IrInstruction *ir_gen_return_from_block(IrBuilder *irb, Scope *break_scop
return ir_build_br(irb, break_scope, node, dest_block, is_comptime); return ir_build_br(irb, break_scope, node, dest_block, is_comptime);
} }
static IrInstruction *ir_gen_break_from_suspend(IrBuilder *irb, Scope *break_scope, AstNode *node, ScopeSuspend *suspend_scope) {
IrInstruction *is_comptime = ir_build_const_bool(irb, break_scope, node, false);
IrBasicBlock *dest_block = suspend_scope->resume_block;
ir_gen_defers_for_block(irb, break_scope, dest_block->scope, false);
return ir_build_br(irb, break_scope, node, dest_block, is_comptime);
}
static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *node) { static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *node) {
assert(node->type == NodeTypeBreak); assert(node->type == NodeTypeBreak);
@ -6202,12 +6226,8 @@ static IrInstruction *ir_gen_break(IrBuilder *irb, Scope *break_scope, AstNode *
return ir_gen_return_from_block(irb, break_scope, node, this_block_scope); return ir_gen_return_from_block(irb, break_scope, node, this_block_scope);
} }
} else if (search_scope->id == ScopeIdSuspend) { } else if (search_scope->id == ScopeIdSuspend) {
ScopeSuspend *this_suspend_scope = (ScopeSuspend *)search_scope; add_node_error(irb->codegen, node, buf_sprintf("cannot break out of suspend block"));
if (node->data.break_expr.name != nullptr && return irb->codegen->invalid_instruction;
(this_suspend_scope->name != nullptr && buf_eql_buf(node->data.break_expr.name, this_suspend_scope->name)))
{
return ir_gen_break_from_suspend(irb, break_scope, node, this_suspend_scope);
}
} }
search_scope = search_scope->parent; search_scope = search_scope->parent;
} }
@ -6643,30 +6663,150 @@ static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNo
async_allocator_type_value, is_var_args); async_allocator_type_value, is_var_args);
} }
static IrInstruction *ir_gen_cancel(IrBuilder *irb, Scope *parent_scope, AstNode *node) { static IrInstruction *ir_gen_cancel_target(IrBuilder *irb, Scope *scope, AstNode *node,
IrInstruction *target_inst, bool cancel_non_suspended, bool cancel_awaited)
{
IrBasicBlock *done_block = ir_create_basic_block(irb, scope, "CancelDone");
IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
IrBasicBlock *pre_return_block = ir_create_basic_block(irb, scope, "PreReturn");
IrBasicBlock *post_return_block = ir_create_basic_block(irb, scope, "PostReturn");
IrBasicBlock *do_cancel_block = ir_create_basic_block(irb, scope, "DoCancel");
IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
IrInstruction *promise_T_type_val = ir_build_const_type(irb, scope, node,
get_promise_type(irb->codegen, irb->codegen->builtin_types.entry_void));
IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
IrInstruction *await_mask = ir_build_const_usize(irb, scope, node, 0x4); // 0b100
IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
// TODO relies on Zig not re-ordering fields
IrInstruction *casted_target_inst = ir_build_ptr_cast(irb, scope, node, promise_T_type_val, target_inst);
IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, casted_target_inst);
Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
atomic_state_field_name);
// set the is_canceled bit
IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
usize_type_val, atomic_state_ptr, nullptr, is_canceled_mask, nullptr,
AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
ir_build_cond_br(irb, scope, node, is_canceled_bool, done_block, not_canceled_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
IrInstruction *awaiter_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
IrInstruction *is_returned_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpEq, awaiter_addr, ptr_mask, false);
ir_build_cond_br(irb, scope, node, is_returned_bool, post_return_block, pre_return_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, post_return_block);
if (cancel_awaited) {
ir_build_br(irb, scope, node, do_cancel_block, is_comptime);
} else {
IrInstruction *is_awaited_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, await_mask, false);
IrInstruction *is_awaited_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_awaited_value, zero, false);
ir_build_cond_br(irb, scope, node, is_awaited_bool, done_block, do_cancel_block, is_comptime);
}
ir_set_cursor_at_end_and_append_block(irb, pre_return_block);
if (cancel_awaited) {
if (cancel_non_suspended) {
ir_build_br(irb, scope, node, do_cancel_block, is_comptime);
} else {
IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
ir_build_cond_br(irb, scope, node, is_suspended_bool, do_cancel_block, done_block, is_comptime);
}
} else {
ir_build_br(irb, scope, node, done_block, is_comptime);
}
ir_set_cursor_at_end_and_append_block(irb, do_cancel_block);
ir_build_cancel(irb, scope, node, target_inst);
ir_build_br(irb, scope, node, done_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, done_block);
return ir_build_const_void(irb, scope, node);
}
static IrInstruction *ir_gen_cancel(IrBuilder *irb, Scope *scope, AstNode *node) {
assert(node->type == NodeTypeCancel); assert(node->type == NodeTypeCancel);
IrInstruction *target_inst = ir_gen_node(irb, node->data.cancel_expr.expr, parent_scope); IrInstruction *target_inst = ir_gen_node(irb, node->data.cancel_expr.expr, scope);
if (target_inst == irb->codegen->invalid_instruction) if (target_inst == irb->codegen->invalid_instruction)
return irb->codegen->invalid_instruction; return irb->codegen->invalid_instruction;
return ir_build_cancel(irb, parent_scope, node, target_inst); return ir_gen_cancel_target(irb, scope, node, target_inst, false, true);
} }
static IrInstruction *ir_gen_resume(IrBuilder *irb, Scope *parent_scope, AstNode *node) { static IrInstruction *ir_gen_resume_target(IrBuilder *irb, Scope *scope, AstNode *node,
IrInstruction *target_inst)
{
IrBasicBlock *done_block = ir_create_basic_block(irb, scope, "ResumeDone");
IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
IrBasicBlock *suspended_block = ir_create_basic_block(irb, scope, "IsSuspended");
IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, scope, "IsNotSuspended");
IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
IrInstruction *and_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, is_suspended_mask);
IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
IrInstruction *promise_T_type_val = ir_build_const_type(irb, scope, node,
get_promise_type(irb->codegen, irb->codegen->builtin_types.entry_void));
// TODO relies on Zig not re-ordering fields
IrInstruction *casted_target_inst = ir_build_ptr_cast(irb, scope, node, promise_T_type_val, target_inst);
IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, casted_target_inst);
Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
atomic_state_field_name);
// clear the is_suspended bit
IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
usize_type_val, atomic_state_ptr, nullptr, and_mask, nullptr,
AtomicRmwOp_and, AtomicOrderSeqCst);
IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
ir_build_cond_br(irb, scope, node, is_canceled_bool, done_block, not_canceled_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
IrInstruction *is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
IrInstruction *is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
ir_build_cond_br(irb, scope, node, is_suspended_bool, suspended_block, not_suspended_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
ir_build_unreachable(irb, scope, node);
ir_set_cursor_at_end_and_append_block(irb, suspended_block);
ir_build_coro_resume(irb, scope, node, target_inst);
ir_build_br(irb, scope, node, done_block, is_comptime);
ir_set_cursor_at_end_and_append_block(irb, done_block);
return ir_build_const_void(irb, scope, node);
}
static IrInstruction *ir_gen_resume(IrBuilder *irb, Scope *scope, AstNode *node) {
assert(node->type == NodeTypeResume); assert(node->type == NodeTypeResume);
IrInstruction *target_inst = ir_gen_node(irb, node->data.resume_expr.expr, parent_scope); IrInstruction *target_inst = ir_gen_node(irb, node->data.resume_expr.expr, scope);
if (target_inst == irb->codegen->invalid_instruction) if (target_inst == irb->codegen->invalid_instruction)
return irb->codegen->invalid_instruction; return irb->codegen->invalid_instruction;
return ir_build_coro_resume(irb, parent_scope, node, target_inst); return ir_gen_resume_target(irb, scope, node, target_inst);
} }
static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *parent_scope, AstNode *node) { static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *scope, AstNode *node) {
assert(node->type == NodeTypeAwaitExpr); assert(node->type == NodeTypeAwaitExpr);
IrInstruction *target_inst = ir_gen_node(irb, node->data.await_expr.expr, parent_scope); IrInstruction *target_inst = ir_gen_node(irb, node->data.await_expr.expr, scope);
if (target_inst == irb->codegen->invalid_instruction) if (target_inst == irb->codegen->invalid_instruction)
return irb->codegen->invalid_instruction; return irb->codegen->invalid_instruction;
@ -6680,7 +6820,7 @@ static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *parent_scope, Ast
return irb->codegen->invalid_instruction; return irb->codegen->invalid_instruction;
} }
ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(parent_scope); ScopeDeferExpr *scope_defer_expr = get_scope_defer_expr(scope);
if (scope_defer_expr) { if (scope_defer_expr) {
if (!scope_defer_expr->reported_err) { if (!scope_defer_expr->reported_err) {
add_node_error(irb->codegen, node, buf_sprintf("cannot await inside defer expression")); add_node_error(irb->codegen, node, buf_sprintf("cannot await inside defer expression"));
@ -6691,81 +6831,157 @@ static IrInstruction *ir_gen_await_expr(IrBuilder *irb, Scope *parent_scope, Ast
Scope *outer_scope = irb->exec->begin_scope; Scope *outer_scope = irb->exec->begin_scope;
IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, parent_scope, node, target_inst); IrInstruction *coro_promise_ptr = ir_build_coro_promise(irb, scope, node, target_inst);
Buf *result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME); Buf *result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME);
IrInstruction *result_ptr_field_ptr = ir_build_field_ptr(irb, parent_scope, node, coro_promise_ptr, result_ptr_field_name); IrInstruction *result_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_ptr_field_name);
if (irb->codegen->have_err_ret_tracing) { if (irb->codegen->have_err_ret_tracing) {
IrInstruction *err_ret_trace_ptr = ir_build_error_return_trace(irb, parent_scope, node, IrInstructionErrorReturnTrace::NonNull); IrInstruction *err_ret_trace_ptr = ir_build_error_return_trace(irb, scope, node, IrInstructionErrorReturnTrace::NonNull);
Buf *err_ret_trace_ptr_field_name = buf_create_from_str(ERR_RET_TRACE_PTR_FIELD_NAME); Buf *err_ret_trace_ptr_field_name = buf_create_from_str(ERR_RET_TRACE_PTR_FIELD_NAME);
IrInstruction *err_ret_trace_ptr_field_ptr = ir_build_field_ptr(irb, parent_scope, node, coro_promise_ptr, err_ret_trace_ptr_field_name); IrInstruction *err_ret_trace_ptr_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_ptr_field_name);
ir_build_store_ptr(irb, parent_scope, node, err_ret_trace_ptr_field_ptr, err_ret_trace_ptr); ir_build_store_ptr(irb, scope, node, err_ret_trace_ptr_field_ptr, err_ret_trace_ptr);
} }
Buf *awaiter_handle_field_name = buf_create_from_str(AWAITER_HANDLE_FIELD_NAME); IrBasicBlock *already_awaited_block = ir_create_basic_block(irb, scope, "AlreadyAwaited");
IrInstruction *awaiter_field_ptr = ir_build_field_ptr(irb, parent_scope, node, coro_promise_ptr, IrBasicBlock *not_awaited_block = ir_create_basic_block(irb, scope, "NotAwaited");
awaiter_handle_field_name); IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, scope, "NotCanceled");
IrBasicBlock *yes_suspend_block = ir_create_basic_block(irb, scope, "YesSuspend");
IrBasicBlock *no_suspend_block = ir_create_basic_block(irb, scope, "NoSuspend");
IrBasicBlock *merge_block = ir_create_basic_block(irb, scope, "MergeSuspend");
IrBasicBlock *cleanup_block = ir_create_basic_block(irb, scope, "SuspendCleanup");
IrBasicBlock *resume_block = ir_create_basic_block(irb, scope, "SuspendResume");
IrBasicBlock *cancel_target_block = ir_create_basic_block(irb, scope, "CancelTarget");
IrBasicBlock *do_cancel_block = ir_create_basic_block(irb, scope, "DoCancel");
IrBasicBlock *do_defers_block = ir_create_basic_block(irb, scope, "DoDefers");
IrBasicBlock *destroy_block = ir_create_basic_block(irb, scope, "DestroyBlock");
IrBasicBlock *my_suspended_block = ir_create_basic_block(irb, scope, "AlreadySuspended");
IrBasicBlock *my_not_suspended_block = ir_create_basic_block(irb, scope, "NotAlreadySuspended");
IrBasicBlock *do_suspend_block = ir_create_basic_block(irb, scope, "DoSuspend");
IrInstruction *const_bool_false = ir_build_const_bool(irb, parent_scope, node, false); Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
VariableTableEntry *result_var = ir_create_var(irb, node, parent_scope, nullptr, IrInstruction *atomic_state_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
atomic_state_field_name);
IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_promise);
IrInstruction *const_bool_false = ir_build_const_bool(irb, scope, node, false);
IrInstruction *undefined_value = ir_build_const_undefined(irb, scope, node);
IrInstruction *usize_type_val = ir_build_const_type(irb, scope, node, irb->codegen->builtin_types.entry_usize);
IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, scope, node, 0x7); // 0b111
IrInstruction *ptr_mask = ir_build_un_op(irb, scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
IrInstruction *await_mask = ir_build_const_usize(irb, scope, node, 0x4); // 0b100
IrInstruction *is_canceled_mask = ir_build_const_usize(irb, scope, node, 0x1); // 0b001
IrInstruction *is_suspended_mask = ir_build_const_usize(irb, scope, node, 0x2); // 0b010
VariableTableEntry *result_var = ir_create_var(irb, node, scope, nullptr,
false, false, true, const_bool_false); false, false, true, const_bool_false);
IrInstruction *undefined_value = ir_build_const_undefined(irb, parent_scope, node); IrInstruction *target_promise_type = ir_build_typeof(irb, scope, node, target_inst);
IrInstruction *target_promise_type = ir_build_typeof(irb, parent_scope, node, target_inst); IrInstruction *promise_result_type = ir_build_promise_result_type(irb, scope, node, target_promise_type);
IrInstruction *promise_result_type = ir_build_promise_result_type(irb, parent_scope, node, target_promise_type); ir_build_await_bookkeeping(irb, scope, node, promise_result_type);
ir_build_await_bookkeeping(irb, parent_scope, node, promise_result_type); ir_build_var_decl(irb, scope, node, result_var, promise_result_type, nullptr, undefined_value);
ir_build_var_decl(irb, parent_scope, node, result_var, promise_result_type, nullptr, undefined_value); IrInstruction *my_result_var_ptr = ir_build_var_ptr(irb, scope, node, result_var);
IrInstruction *my_result_var_ptr = ir_build_var_ptr(irb, parent_scope, node, result_var); ir_build_store_ptr(irb, scope, node, result_ptr_field_ptr, my_result_var_ptr);
ir_build_store_ptr(irb, parent_scope, node, result_ptr_field_ptr, my_result_var_ptr); IrInstruction *save_token = ir_build_coro_save(irb, scope, node, irb->exec->coro_handle);
IrInstruction *save_token = ir_build_coro_save(irb, parent_scope, node, irb->exec->coro_handle);
IrInstruction *promise_type_val = ir_build_const_type(irb, parent_scope, node, IrInstruction *coro_handle_addr = ir_build_ptr_to_int(irb, scope, node, irb->exec->coro_handle);
get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise)); IrInstruction *mask_bits = ir_build_bin_op(irb, scope, node, IrBinOpBinOr, coro_handle_addr, await_mask, false);
IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, parent_scope, node, IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
promise_type_val, awaiter_field_ptr, nullptr, irb->exec->coro_handle, nullptr, usize_type_val, atomic_state_ptr, nullptr, mask_bits, nullptr,
AtomicRmwOp_xchg, AtomicOrderSeqCst); AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *is_non_null = ir_build_test_nonnull(irb, parent_scope, node, maybe_await_handle);
IrBasicBlock *yes_suspend_block = ir_create_basic_block(irb, parent_scope, "YesSuspend"); IrInstruction *is_awaited_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, await_mask, false);
IrBasicBlock *no_suspend_block = ir_create_basic_block(irb, parent_scope, "NoSuspend"); IrInstruction *is_awaited_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_awaited_value, zero, false);
IrBasicBlock *merge_block = ir_create_basic_block(irb, parent_scope, "MergeSuspend"); ir_build_cond_br(irb, scope, node, is_awaited_bool, already_awaited_block, not_awaited_block, const_bool_false);
ir_build_cond_br(irb, parent_scope, node, is_non_null, no_suspend_block, yes_suspend_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, already_awaited_block);
ir_build_unreachable(irb, scope, node);
ir_set_cursor_at_end_and_append_block(irb, not_awaited_block);
IrInstruction *await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
IrInstruction *is_non_null = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
IrInstruction *is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
IrInstruction *is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
ir_build_cond_br(irb, scope, node, is_canceled_bool, cancel_target_block, not_canceled_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
ir_build_cond_br(irb, scope, node, is_non_null, no_suspend_block, yes_suspend_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, cancel_target_block);
ir_build_cancel(irb, scope, node, target_inst);
ir_mark_gen(ir_build_br(irb, scope, node, cleanup_block, const_bool_false));
ir_set_cursor_at_end_and_append_block(irb, no_suspend_block); ir_set_cursor_at_end_and_append_block(irb, no_suspend_block);
if (irb->codegen->have_err_ret_tracing) { if (irb->codegen->have_err_ret_tracing) {
Buf *err_ret_trace_field_name = buf_create_from_str(ERR_RET_TRACE_FIELD_NAME); Buf *err_ret_trace_field_name = buf_create_from_str(ERR_RET_TRACE_FIELD_NAME);
IrInstruction *src_err_ret_trace_ptr = ir_build_field_ptr(irb, parent_scope, node, coro_promise_ptr, err_ret_trace_field_name); IrInstruction *src_err_ret_trace_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, err_ret_trace_field_name);
IrInstruction *dest_err_ret_trace_ptr = ir_build_error_return_trace(irb, parent_scope, node, IrInstructionErrorReturnTrace::NonNull); IrInstruction *dest_err_ret_trace_ptr = ir_build_error_return_trace(irb, scope, node, IrInstructionErrorReturnTrace::NonNull);
ir_build_merge_err_ret_traces(irb, parent_scope, node, coro_promise_ptr, src_err_ret_trace_ptr, dest_err_ret_trace_ptr); ir_build_merge_err_ret_traces(irb, scope, node, coro_promise_ptr, src_err_ret_trace_ptr, dest_err_ret_trace_ptr);
} }
Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME); Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME);
IrInstruction *promise_result_ptr = ir_build_field_ptr(irb, parent_scope, node, coro_promise_ptr, result_field_name); IrInstruction *promise_result_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_field_name);
// If the type of the result handle_is_ptr then this does not actually perform a load. But we need it to, // If the type of the result handle_is_ptr then this does not actually perform a load. But we need it to,
// because we're about to destroy the memory. So we store it into our result variable. // because we're about to destroy the memory. So we store it into our result variable.
IrInstruction *no_suspend_result = ir_build_load_ptr(irb, parent_scope, node, promise_result_ptr); IrInstruction *no_suspend_result = ir_build_load_ptr(irb, scope, node, promise_result_ptr);
ir_build_store_ptr(irb, parent_scope, node, my_result_var_ptr, no_suspend_result); ir_build_store_ptr(irb, scope, node, my_result_var_ptr, no_suspend_result);
ir_build_cancel(irb, parent_scope, node, target_inst); ir_build_cancel(irb, scope, node, target_inst);
ir_build_br(irb, parent_scope, node, merge_block, const_bool_false); ir_build_br(irb, scope, node, merge_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, yes_suspend_block); ir_set_cursor_at_end_and_append_block(irb, yes_suspend_block);
IrInstruction *suspend_code = ir_build_coro_suspend(irb, parent_scope, node, save_token, const_bool_false); IrInstruction *my_prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup"); usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, is_suspended_mask, nullptr,
IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume"); AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *my_is_suspended_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, my_prev_atomic_value, is_suspended_mask, false);
IrInstruction *my_is_suspended_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, my_is_suspended_value, zero, false);
ir_build_cond_br(irb, scope, node, my_is_suspended_bool, my_suspended_block, my_not_suspended_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, my_suspended_block);
ir_build_unreachable(irb, scope, node);
ir_set_cursor_at_end_and_append_block(irb, my_not_suspended_block);
IrInstruction *my_is_canceled_value = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, my_prev_atomic_value, is_canceled_mask, false);
IrInstruction *my_is_canceled_bool = ir_build_bin_op(irb, scope, node, IrBinOpCmpNotEq, my_is_canceled_value, zero, false);
ir_build_cond_br(irb, scope, node, my_is_canceled_bool, cleanup_block, do_suspend_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, do_suspend_block);
IrInstruction *suspend_code = ir_build_coro_suspend(irb, scope, node, save_token, const_bool_false);
IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2); IrInstructionSwitchBrCase *cases = allocate<IrInstructionSwitchBrCase>(2);
cases[0].value = ir_build_const_u8(irb, parent_scope, node, 0); cases[0].value = ir_build_const_u8(irb, scope, node, 0);
cases[0].block = resume_block; cases[0].block = resume_block;
cases[1].value = ir_build_const_u8(irb, parent_scope, node, 1); cases[1].value = ir_build_const_u8(irb, scope, node, 1);
cases[1].block = cleanup_block; cases[1].block = destroy_block;
ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block, ir_build_switch_br(irb, scope, node, suspend_code, irb->exec->coro_suspend_block,
2, cases, const_bool_false, nullptr); 2, cases, const_bool_false, nullptr);
ir_set_cursor_at_end_and_append_block(irb, destroy_block);
ir_gen_cancel_target(irb, scope, node, target_inst, false, true);
ir_mark_gen(ir_build_br(irb, scope, node, cleanup_block, const_bool_false));
ir_set_cursor_at_end_and_append_block(irb, cleanup_block); ir_set_cursor_at_end_and_append_block(irb, cleanup_block);
ir_gen_defers_for_block(irb, parent_scope, outer_scope, true); IrInstruction *my_mask_bits = ir_build_bin_op(irb, scope, node, IrBinOpBinOr, ptr_mask, is_canceled_mask, false);
ir_mark_gen(ir_build_br(irb, parent_scope, node, irb->exec->coro_final_cleanup_block, const_bool_false)); IrInstruction *b_my_prev_atomic_value = ir_build_atomic_rmw(irb, scope, node,
usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, my_mask_bits, nullptr,
AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *my_await_handle_addr = ir_build_bin_op(irb, scope, node, IrBinOpBinAnd, b_my_prev_atomic_value, ptr_mask, false);
IrInstruction *dont_have_my_await_handle = ir_build_bin_op(irb, scope, node, IrBinOpCmpEq, my_await_handle_addr, zero, false);
IrInstruction *dont_destroy_ourselves = ir_build_bin_op(irb, scope, node, IrBinOpBoolAnd, dont_have_my_await_handle, is_canceled_bool, false);
ir_build_cond_br(irb, scope, node, dont_have_my_await_handle, do_defers_block, do_cancel_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, do_cancel_block);
IrInstruction *my_await_handle = ir_build_int_to_ptr(irb, scope, node, promise_type_val, my_await_handle_addr);
ir_gen_cancel_target(irb, scope, node, my_await_handle, true, false);
ir_mark_gen(ir_build_br(irb, scope, node, do_defers_block, const_bool_false));
ir_set_cursor_at_end_and_append_block(irb, do_defers_block);
ir_gen_defers_for_block(irb, scope, outer_scope, true);
ir_mark_gen(ir_build_cond_br(irb, scope, node, dont_destroy_ourselves, irb->exec->coro_early_final, irb->exec->coro_final_cleanup_block, const_bool_false));
ir_set_cursor_at_end_and_append_block(irb, resume_block); ir_set_cursor_at_end_and_append_block(irb, resume_block);
ir_build_br(irb, parent_scope, node, merge_block, const_bool_false); ir_build_br(irb, scope, node, merge_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, merge_block); ir_set_cursor_at_end_and_append_block(irb, merge_block);
return ir_build_load_ptr(irb, parent_scope, node, my_result_var_ptr); return ir_build_load_ptr(irb, scope, node, my_result_var_ptr);
} }
static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNode *node) { static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNode *node) {
@ -6804,9 +7020,52 @@ static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNod
IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup"); IrBasicBlock *cleanup_block = ir_create_basic_block(irb, parent_scope, "SuspendCleanup");
IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume"); IrBasicBlock *resume_block = ir_create_basic_block(irb, parent_scope, "SuspendResume");
IrBasicBlock *suspended_block = ir_create_basic_block(irb, parent_scope, "AlreadySuspended");
IrBasicBlock *canceled_block = ir_create_basic_block(irb, parent_scope, "IsCanceled");
IrBasicBlock *not_canceled_block = ir_create_basic_block(irb, parent_scope, "NotCanceled");
IrBasicBlock *not_suspended_block = ir_create_basic_block(irb, parent_scope, "NotAlreadySuspended");
IrBasicBlock *cancel_awaiter_block = ir_create_basic_block(irb, parent_scope, "CancelAwaiter");
IrInstruction *suspend_code; IrInstruction *promise_type_val = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_promise);
IrInstruction *const_bool_true = ir_build_const_bool(irb, parent_scope, node, true);
IrInstruction *const_bool_false = ir_build_const_bool(irb, parent_scope, node, false); IrInstruction *const_bool_false = ir_build_const_bool(irb, parent_scope, node, false);
IrInstruction *usize_type_val = ir_build_const_type(irb, parent_scope, node, irb->codegen->builtin_types.entry_usize);
IrInstruction *is_canceled_mask = ir_build_const_usize(irb, parent_scope, node, 0x1); // 0b001
IrInstruction *is_suspended_mask = ir_build_const_usize(irb, parent_scope, node, 0x2); // 0b010
IrInstruction *zero = ir_build_const_usize(irb, parent_scope, node, 0);
IrInstruction *inverted_ptr_mask = ir_build_const_usize(irb, parent_scope, node, 0x7); // 0b111
IrInstruction *ptr_mask = ir_build_un_op(irb, parent_scope, node, IrUnOpBinNot, inverted_ptr_mask); // 0b111...000
IrInstruction *prev_atomic_value = ir_build_atomic_rmw(irb, parent_scope, node,
usize_type_val, irb->exec->atomic_state_field_ptr, nullptr, is_suspended_mask, nullptr,
AtomicRmwOp_or, AtomicOrderSeqCst);
IrInstruction *is_canceled_value = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, is_canceled_mask, false);
IrInstruction *is_canceled_bool = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, is_canceled_value, zero, false);
ir_build_cond_br(irb, parent_scope, node, is_canceled_bool, canceled_block, not_canceled_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, canceled_block);
IrInstruction *await_handle_addr = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, ptr_mask, false);
IrInstruction *have_await_handle = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, await_handle_addr, zero, false);
IrBasicBlock *post_canceled_block = irb->current_basic_block;
ir_build_cond_br(irb, parent_scope, node, have_await_handle, cancel_awaiter_block, cleanup_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, cancel_awaiter_block);
IrInstruction *await_handle = ir_build_int_to_ptr(irb, parent_scope, node, promise_type_val, await_handle_addr);
ir_gen_cancel_target(irb, parent_scope, node, await_handle, true, false);
IrBasicBlock *post_cancel_awaiter_block = irb->current_basic_block;
ir_build_br(irb, parent_scope, node, cleanup_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, not_canceled_block);
IrInstruction *is_suspended_value = ir_build_bin_op(irb, parent_scope, node, IrBinOpBinAnd, prev_atomic_value, is_suspended_mask, false);
IrInstruction *is_suspended_bool = ir_build_bin_op(irb, parent_scope, node, IrBinOpCmpNotEq, is_suspended_value, zero, false);
ir_build_cond_br(irb, parent_scope, node, is_suspended_bool, suspended_block, not_suspended_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, suspended_block);
ir_build_unreachable(irb, parent_scope, node);
ir_set_cursor_at_end_and_append_block(irb, not_suspended_block);
IrInstruction *suspend_code;
if (node->data.suspend.block == nullptr) { if (node->data.suspend.block == nullptr) {
suspend_code = ir_build_coro_suspend(irb, parent_scope, node, nullptr, const_bool_false); suspend_code = ir_build_coro_suspend(irb, parent_scope, node, nullptr, const_bool_false);
} else { } else {
@ -6834,13 +7093,20 @@ static IrInstruction *ir_gen_suspend(IrBuilder *irb, Scope *parent_scope, AstNod
cases[0].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 0)); cases[0].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 0));
cases[0].block = resume_block; cases[0].block = resume_block;
cases[1].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 1)); cases[1].value = ir_mark_gen(ir_build_const_u8(irb, parent_scope, node, 1));
cases[1].block = cleanup_block; cases[1].block = canceled_block;
ir_mark_gen(ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block, ir_mark_gen(ir_build_switch_br(irb, parent_scope, node, suspend_code, irb->exec->coro_suspend_block,
2, cases, const_bool_false, nullptr)); 2, cases, const_bool_false, nullptr));
ir_set_cursor_at_end_and_append_block(irb, cleanup_block); ir_set_cursor_at_end_and_append_block(irb, cleanup_block);
IrBasicBlock **incoming_blocks = allocate<IrBasicBlock *>(2);
IrInstruction **incoming_values = allocate<IrInstruction *>(2);
incoming_blocks[0] = post_canceled_block;
incoming_values[0] = const_bool_true;
incoming_blocks[1] = post_cancel_awaiter_block;
incoming_values[1] = const_bool_false;
IrInstruction *destroy_ourselves = ir_build_phi(irb, parent_scope, node, 2, incoming_blocks, incoming_values);
ir_gen_defers_for_block(irb, parent_scope, outer_scope, true); ir_gen_defers_for_block(irb, parent_scope, outer_scope, true);
ir_mark_gen(ir_build_br(irb, parent_scope, node, irb->exec->coro_final_cleanup_block, const_bool_false)); ir_mark_gen(ir_build_cond_br(irb, parent_scope, node, destroy_ourselves, irb->exec->coro_final_cleanup_block, irb->exec->coro_early_final, const_bool_false));
ir_set_cursor_at_end_and_append_block(irb, resume_block); ir_set_cursor_at_end_and_append_block(irb, resume_block);
return ir_mark_gen(ir_build_const_void(irb, parent_scope, node)); return ir_mark_gen(ir_build_const_void(irb, parent_scope, node));
@ -7081,10 +7347,11 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
IrInstruction *coro_mem_ptr = ir_build_ptr_cast(irb, coro_scope, node, u8_ptr_type, maybe_coro_mem_ptr); IrInstruction *coro_mem_ptr = ir_build_ptr_cast(irb, coro_scope, node, u8_ptr_type, maybe_coro_mem_ptr);
irb->exec->coro_handle = ir_build_coro_begin(irb, coro_scope, node, coro_id, coro_mem_ptr); irb->exec->coro_handle = ir_build_coro_begin(irb, coro_scope, node, coro_id, coro_mem_ptr);
Buf *awaiter_handle_field_name = buf_create_from_str(AWAITER_HANDLE_FIELD_NAME); Buf *atomic_state_field_name = buf_create_from_str(ATOMIC_STATE_FIELD_NAME);
irb->exec->coro_awaiter_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, irb->exec->atomic_state_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr,
awaiter_handle_field_name); atomic_state_field_name);
ir_build_store_ptr(irb, scope, node, irb->exec->coro_awaiter_field_ptr, null_value); IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
ir_build_store_ptr(irb, scope, node, irb->exec->atomic_state_field_ptr, zero);
Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME); Buf *result_field_name = buf_create_from_str(RESULT_FIELD_NAME);
irb->exec->coro_result_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_field_name); irb->exec->coro_result_field_ptr = ir_build_field_ptr(irb, scope, node, coro_promise_ptr, result_field_name);
result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME); result_ptr_field_name = buf_create_from_str(RESULT_PTR_FIELD_NAME);
@ -7102,7 +7369,6 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
// coordinate with builtin.zig // coordinate with builtin.zig
Buf *index_name = buf_create_from_str("index"); Buf *index_name = buf_create_from_str("index");
IrInstruction *index_ptr = ir_build_field_ptr(irb, scope, node, err_ret_trace_ptr, index_name); IrInstruction *index_ptr = ir_build_field_ptr(irb, scope, node, err_ret_trace_ptr, index_name);
IrInstruction *zero = ir_build_const_usize(irb, scope, node, 0);
ir_build_store_ptr(irb, scope, node, index_ptr, zero); ir_build_store_ptr(irb, scope, node, index_ptr, zero);
Buf *instruction_addresses_name = buf_create_from_str("instruction_addresses"); Buf *instruction_addresses_name = buf_create_from_str("instruction_addresses");
@ -7225,7 +7491,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
ir_build_cond_br(irb, scope, node, resume_awaiter, resume_block, irb->exec->coro_suspend_block, const_bool_false); ir_build_cond_br(irb, scope, node, resume_awaiter, resume_block, irb->exec->coro_suspend_block, const_bool_false);
ir_set_cursor_at_end_and_append_block(irb, resume_block); ir_set_cursor_at_end_and_append_block(irb, resume_block);
ir_build_coro_resume(irb, scope, node, awaiter_handle); ir_gen_resume_target(irb, scope, node, awaiter_handle);
ir_build_br(irb, scope, node, irb->exec->coro_suspend_block, const_bool_false); ir_build_br(irb, scope, node, irb->exec->coro_suspend_block, const_bool_false);
} }
@ -12142,7 +12408,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc
result_type = ira->codegen->builtin_types.entry_invalid; result_type = ira->codegen->builtin_types.entry_invalid;
} else if (type_requires_comptime(result_type)) { } else if (type_requires_comptime(result_type)) {
var_class_requires_const = true; var_class_requires_const = true;
if (!var->src_is_const && !is_comptime_var) { if (!var->gen_is_const && !is_comptime_var) {
ir_add_error_node(ira, source_node, ir_add_error_node(ira, source_node,
buf_sprintf("variable of type '%s' must be const or comptime", buf_sprintf("variable of type '%s' must be const or comptime",
buf_ptr(&result_type->name))); buf_ptr(&result_type->name)));
@ -12591,6 +12857,7 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod
} }
Buf *param_name = param_decl_node->data.param_decl.name; Buf *param_name = param_decl_node->data.param_decl.name;
if (!param_name) return false;
if (!is_var_args) { if (!is_var_args) {
VariableTableEntry *var = add_variable(ira->codegen, param_decl_node, VariableTableEntry *var = add_variable(ira->codegen, param_decl_node,
*child_scope, param_name, true, arg_val, nullptr); *child_scope, param_name, true, arg_val, nullptr);
@ -18991,6 +19258,9 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_err_payload(IrAnalyze *ira,
return ira->codegen->builtin_types.entry_invalid; return ira->codegen->builtin_types.entry_invalid;
} else if (type_entry->id == TypeTableEntryIdErrorUnion) { } else if (type_entry->id == TypeTableEntryIdErrorUnion) {
TypeTableEntry *payload_type = type_entry->data.error_union.payload_type; TypeTableEntry *payload_type = type_entry->data.error_union.payload_type;
if (type_is_invalid(payload_type)) {
return ira->codegen->builtin_types.entry_invalid;
}
TypeTableEntry *result_type = get_pointer_to_type_extra(ira->codegen, payload_type, TypeTableEntry *result_type = get_pointer_to_type_extra(ira->codegen, payload_type,
ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile, ptr_type->data.pointer.is_const, ptr_type->data.pointer.is_volatile,
PtrLenSingle, PtrLenSingle,

View File

@ -45,6 +45,10 @@ static void ir_print_var_instruction(IrPrint *irp, IrInstruction *instruction) {
} }
static void ir_print_other_instruction(IrPrint *irp, IrInstruction *instruction) { static void ir_print_other_instruction(IrPrint *irp, IrInstruction *instruction) {
if (instruction == nullptr) {
fprintf(irp->f, "(null)");
return;
}
if (instruction->value.special != ConstValSpecialRuntime) { if (instruction->value.special != ConstValSpecialRuntime) {
ir_print_const_value(irp, &instruction->value); ir_print_const_value(irp, &instruction->value);
} else { } else {

View File

@ -648,30 +648,12 @@ static AstNode *ast_parse_asm_expr(ParseContext *pc, size_t *token_index, bool m
} }
/* /*
SuspendExpression(body) = option(Symbol ":") "suspend" option(("|" Symbol "|" body)) SuspendExpression(body) = "suspend" option(("|" Symbol "|" body))
*/ */
static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, bool mandatory) { static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, bool mandatory) {
size_t orig_token_index = *token_index; size_t orig_token_index = *token_index;
Token *name_token = nullptr; Token *suspend_token = &pc->tokens->at(*token_index);
Token *token = &pc->tokens->at(*token_index);
if (token->id == TokenIdSymbol) {
*token_index += 1;
Token *colon_token = &pc->tokens->at(*token_index);
if (colon_token->id == TokenIdColon) {
*token_index += 1;
name_token = token;
token = &pc->tokens->at(*token_index);
} else if (mandatory) {
ast_expect_token(pc, colon_token, TokenIdColon);
zig_unreachable();
} else {
*token_index = orig_token_index;
return nullptr;
}
}
Token *suspend_token = token;
if (suspend_token->id == TokenIdKeywordSuspend) { if (suspend_token->id == TokenIdKeywordSuspend) {
*token_index += 1; *token_index += 1;
} else if (mandatory) { } else if (mandatory) {
@ -693,9 +675,6 @@ static AstNode *ast_parse_suspend_block(ParseContext *pc, size_t *token_index, b
} }
AstNode *node = ast_create_node(pc, NodeTypeSuspend, suspend_token); AstNode *node = ast_create_node(pc, NodeTypeSuspend, suspend_token);
if (name_token != nullptr) {
node->data.suspend.name = token_buf(name_token);
}
node->data.suspend.promise_symbol = ast_parse_symbol(pc, token_index); node->data.suspend.promise_symbol = ast_parse_symbol(pc, token_index);
ast_eat_token(pc, token_index, TokenIdBinOr); ast_eat_token(pc, token_index, TokenIdBinOr);
node->data.suspend.block = ast_parse_block(pc, token_index, true); node->data.suspend.block = ast_parse_block(pc, token_index, true);

View File

@ -807,6 +807,7 @@ pub const LibExeObjStep = struct {
disable_libc: bool, disable_libc: bool,
frameworks: BufSet, frameworks: BufSet,
verbose_link: bool, verbose_link: bool,
no_rosegment: bool,
// zig only stuff // zig only stuff
root_src: ?[]const u8, root_src: ?[]const u8,
@ -874,6 +875,7 @@ pub const LibExeObjStep = struct {
fn initExtraArgs(builder: *Builder, name: []const u8, root_src: ?[]const u8, kind: Kind, static: bool, ver: *const Version) LibExeObjStep { fn initExtraArgs(builder: *Builder, name: []const u8, root_src: ?[]const u8, kind: Kind, static: bool, ver: *const Version) LibExeObjStep {
var self = LibExeObjStep{ var self = LibExeObjStep{
.no_rosegment = false,
.strip = false, .strip = false,
.builder = builder, .builder = builder,
.verbose_link = false, .verbose_link = false,
@ -914,6 +916,7 @@ pub const LibExeObjStep = struct {
fn initC(builder: *Builder, name: []const u8, kind: Kind, version: *const Version, static: bool) LibExeObjStep { fn initC(builder: *Builder, name: []const u8, kind: Kind, version: *const Version, static: bool) LibExeObjStep {
var self = LibExeObjStep{ var self = LibExeObjStep{
.no_rosegment = false,
.builder = builder, .builder = builder,
.name = name, .name = name,
.kind = kind, .kind = kind,
@ -953,6 +956,10 @@ pub const LibExeObjStep = struct {
return self; return self;
} }
pub fn setNoRoSegment(self: *LibExeObjStep, value: bool) void {
self.no_rosegment = value;
}
fn computeOutFileNames(self: *LibExeObjStep) void { fn computeOutFileNames(self: *LibExeObjStep) void {
switch (self.kind) { switch (self.kind) {
Kind.Obj => { Kind.Obj => {
@ -1306,6 +1313,10 @@ pub const LibExeObjStep = struct {
} }
} }
if (self.no_rosegment) {
try zig_args.append("--no-rosegment");
}
try builder.spawnChild(zig_args.toSliceConst()); try builder.spawnChild(zig_args.toSliceConst());
if (self.kind == Kind.Lib and !self.static and self.target.wantSharedLibSymLinks()) { if (self.kind == Kind.Lib and !self.static and self.target.wantSharedLibSymLinks()) {
@ -1598,6 +1609,7 @@ pub const TestStep = struct {
include_dirs: ArrayList([]const u8), include_dirs: ArrayList([]const u8),
lib_paths: ArrayList([]const u8), lib_paths: ArrayList([]const u8),
object_files: ArrayList([]const u8), object_files: ArrayList([]const u8),
no_rosegment: bool,
pub fn init(builder: *Builder, root_src: []const u8) TestStep { pub fn init(builder: *Builder, root_src: []const u8) TestStep {
const step_name = builder.fmt("test {}", root_src); const step_name = builder.fmt("test {}", root_src);
@ -1615,9 +1627,14 @@ pub const TestStep = struct {
.include_dirs = ArrayList([]const u8).init(builder.allocator), .include_dirs = ArrayList([]const u8).init(builder.allocator),
.lib_paths = ArrayList([]const u8).init(builder.allocator), .lib_paths = ArrayList([]const u8).init(builder.allocator),
.object_files = ArrayList([]const u8).init(builder.allocator), .object_files = ArrayList([]const u8).init(builder.allocator),
.no_rosegment = false,
}; };
} }
pub fn setNoRoSegment(self: *TestStep, value: bool) void {
self.no_rosegment = value;
}
pub fn addLibPath(self: *TestStep, path: []const u8) void { pub fn addLibPath(self: *TestStep, path: []const u8) void {
self.lib_paths.append(path) catch unreachable; self.lib_paths.append(path) catch unreachable;
} }
@ -1761,6 +1778,10 @@ pub const TestStep = struct {
try zig_args.append(lib_path); try zig_args.append(lib_path);
} }
if (self.no_rosegment) {
try zig_args.append("--no-rosegment");
}
try builder.spawnChild(zig_args.toSliceConst()); try builder.spawnChild(zig_args.toSliceConst());
} }
}; };

View File

@ -27,7 +27,7 @@ pub fn warn(comptime fmt: []const u8, args: ...) void {
const stderr = getStderrStream() catch return; const stderr = getStderrStream() catch return;
stderr.print(fmt, args) catch return; stderr.print(fmt, args) catch return;
} }
fn getStderrStream() !*io.OutStream(io.FileOutStream.Error) { pub fn getStderrStream() !*io.OutStream(io.FileOutStream.Error) {
if (stderr_stream) |st| { if (stderr_stream) |st| {
return st; return st;
} else { } else {
@ -172,6 +172,16 @@ pub fn writeStackTrace(stack_trace: *const builtin.StackTrace, out_stream: var,
} }
} }
pub inline fn getReturnAddress(frame_count: usize) usize {
var fp = @ptrToInt(@frameAddress());
var i: usize = 0;
while (fp != 0 and i < frame_count) {
fp = @intToPtr(*const usize, fp).*;
i += 1;
}
return @intToPtr(*const usize, fp + @sizeOf(usize)).*;
}
pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_info: *ElfStackTrace, tty_color: bool, start_addr: ?usize) !void { pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_info: *ElfStackTrace, tty_color: bool, start_addr: ?usize) !void {
const AddressState = union(enum) { const AddressState = union(enum) {
NotLookingForStartAddress, NotLookingForStartAddress,
@ -205,7 +215,7 @@ pub fn writeCurrentStackTrace(out_stream: var, allocator: *mem.Allocator, debug_
} }
} }
fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address: usize, tty_color: bool) !void { pub fn printSourceAtAddress(debug_info: *ElfStackTrace, out_stream: var, address: usize, tty_color: bool) !void {
switch (builtin.os) { switch (builtin.os) {
builtin.Os.windows => return error.UnsupportedDebugInfo, builtin.Os.windows => return error.UnsupportedDebugInfo,
builtin.Os.macosx => { builtin.Os.macosx => {

View File

@ -71,11 +71,6 @@ pub fn Channel(comptime T: type) type {
/// puts a data item in the channel. The promise completes when the value has been added to the /// puts a data item in the channel. The promise completes when the value has been added to the
/// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter. /// buffer, or in the case of a zero size buffer, when the item has been retrieved by a getter.
pub async fn put(self: *SelfChannel, data: T) void { pub async fn put(self: *SelfChannel, data: T) void {
// TODO should be able to group memory allocation failure before first suspend point
// so that the async invocation catches it
var dispatch_tick_node_ptr: *Loop.NextTickNode = undefined;
_ = async self.dispatch(&dispatch_tick_node_ptr) catch unreachable;
suspend |handle| { suspend |handle| {
var my_tick_node = Loop.NextTickNode{ var my_tick_node = Loop.NextTickNode{
.next = undefined, .next = undefined,
@ -91,18 +86,13 @@ pub fn Channel(comptime T: type) type {
self.putters.put(&queue_node); self.putters.put(&queue_node);
_ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); _ = @atomicRmw(usize, &self.put_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
self.loop.onNextTick(dispatch_tick_node_ptr); self.dispatch();
} }
} }
/// await this function to get an item from the channel. If the buffer is empty, the promise will /// await this function to get an item from the channel. If the buffer is empty, the promise will
/// complete when the next item is put in the channel. /// complete when the next item is put in the channel.
pub async fn get(self: *SelfChannel) T { pub async fn get(self: *SelfChannel) T {
// TODO should be able to group memory allocation failure before first suspend point
// so that the async invocation catches it
var dispatch_tick_node_ptr: *Loop.NextTickNode = undefined;
_ = async self.dispatch(&dispatch_tick_node_ptr) catch unreachable;
// TODO integrate this function with named return values // TODO integrate this function with named return values
// so we can get rid of this extra result copy // so we can get rid of this extra result copy
var result: T = undefined; var result: T = undefined;
@ -121,21 +111,12 @@ pub fn Channel(comptime T: type) type {
self.getters.put(&queue_node); self.getters.put(&queue_node);
_ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst); _ = @atomicRmw(usize, &self.get_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
self.loop.onNextTick(dispatch_tick_node_ptr); self.dispatch();
} }
return result; return result;
} }
async fn dispatch(self: *SelfChannel, tick_node_ptr: **Loop.NextTickNode) void { fn dispatch(self: *SelfChannel) void {
// resumed by onNextTick
suspend |handle| {
var tick_node = Loop.NextTickNode{
.data = handle,
.next = undefined,
};
tick_node_ptr.* = &tick_node;
}
// set the "need dispatch" flag // set the "need dispatch" flag
_ = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst); _ = @atomicRmw(u8, &self.need_dispatch, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);

View File

@ -55,7 +55,7 @@ pub const Loop = struct {
/// After initialization, call run(). /// After initialization, call run().
/// TODO copy elision / named return values so that the threads referencing *Loop /// TODO copy elision / named return values so that the threads referencing *Loop
/// have the correct pointer value. /// have the correct pointer value.
fn initSingleThreaded(self: *Loop, allocator: *mem.Allocator) !void { pub fn initSingleThreaded(self: *Loop, allocator: *mem.Allocator) !void {
return self.initInternal(allocator, 1); return self.initInternal(allocator, 1);
} }
@ -64,7 +64,7 @@ pub const Loop = struct {
/// After initialization, call run(). /// After initialization, call run().
/// TODO copy elision / named return values so that the threads referencing *Loop /// TODO copy elision / named return values so that the threads referencing *Loop
/// have the correct pointer value. /// have the correct pointer value.
fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void { pub fn initMultiThreaded(self: *Loop, allocator: *mem.Allocator) !void {
const core_count = try std.os.cpuCount(allocator); const core_count = try std.os.cpuCount(allocator);
return self.initInternal(allocator, core_count); return self.initInternal(allocator, core_count);
} }

View File

@ -18,6 +18,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
OpenBrace, OpenBrace,
CloseBrace, CloseBrace,
FormatString, FormatString,
Pointer,
}; };
comptime var start_index = 0; comptime var start_index = 0;
@ -54,6 +55,7 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
state = State.Start; state = State.Start;
start_index = i + 1; start_index = i + 1;
}, },
'*' => state = State.Pointer,
else => { else => {
state = State.FormatString; state = State.FormatString;
}, },
@ -75,6 +77,17 @@ pub fn format(context: var, comptime Errors: type, output: fn (@typeOf(context),
}, },
else => {}, else => {},
}, },
State.Pointer => switch (c) {
'}' => {
try output(context, @typeName(@typeOf(args[next_arg]).Child));
try output(context, "@");
try formatInt(@ptrToInt(args[next_arg]), 16, false, 0, context, Errors, output);
next_arg += 1;
state = State.Start;
start_index = i + 1;
},
else => @compileError("Unexpected format character after '*'"),
},
} }
} }
comptime { comptime {
@ -861,6 +874,27 @@ test "fmt.format" {
const value: u8 = 'a'; const value: u8 = 'a';
try testFmt("u8: a\n", "u8: {c}\n", value); try testFmt("u8: a\n", "u8: {c}\n", value);
} }
{
const value: [3]u8 = "abc";
try testFmt("array: abc\n", "array: {}\n", value);
try testFmt("array: abc\n", "array: {}\n", &value);
var buf: [100]u8 = undefined;
try testFmt(
try bufPrint(buf[0..], "array: [3]u8@{x}\n", @ptrToInt(&value)),
"array: {*}\n",
&value,
);
}
{
const value: []const u8 = "abc";
try testFmt("slice: abc\n", "slice: {}\n", value);
}
{
const value = @intToPtr(*i32, 0xdeadbeef);
try testFmt("pointer: i32@deadbeef\n", "pointer: {}\n", value);
try testFmt("pointer: i32@deadbeef\n", "pointer: {*}\n", value);
}
try testFmt("buf: Test \n", "buf: {s5}\n", "Test"); try testFmt("buf: Test \n", "buf: {s5}\n", "Test");
try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test"); try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", "Test");
try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C"); try testFmt("cstr: Test C\n", "cstr: {s}\n", c"Test C");

View File

@ -200,6 +200,13 @@ pub fn InStream(comptime ReadError: type) type {
try self.readNoEof(input_slice); try self.readNoEof(input_slice);
return mem.readInt(input_slice, T, endian); return mem.readInt(input_slice, T, endian);
} }
pub fn skipBytes(self: *Self, num_bytes: usize) !void {
var i: usize = 0;
while (i < num_bytes) : (i += 1) {
_ = try self.readByte();
}
}
}; };
} }
@ -230,6 +237,20 @@ pub fn OutStream(comptime WriteError: type) type {
try self.writeFn(self, slice); try self.writeFn(self, slice);
} }
} }
pub fn writeIntLe(self: *Self, comptime T: type, value: T) !void {
return self.writeInt(builtin.Endian.Little, T, value);
}
pub fn writeIntBe(self: *Self, comptime T: type, value: T) !void {
return self.writeInt(builtin.Endian.Big, T, value);
}
pub fn writeInt(self: *Self, endian: builtin.Endian, comptime T: type, value: T) !void {
var bytes: [@sizeOf(T)]u8 = undefined;
mem.writeInt(bytes[0..], value, endian);
return self.writeFn(self, bytes);
}
}; };
} }
@ -331,6 +352,150 @@ pub fn BufferedInStreamCustom(comptime buffer_size: usize, comptime Error: type)
}; };
} }
/// Creates a stream which supports 'un-reading' data, so that it can be read again.
/// This makes look-ahead style parsing much easier.
pub fn PeekStream(comptime buffer_size: usize, comptime InStreamError: type) type {
return struct {
const Self = this;
pub const Error = InStreamError;
pub const Stream = InStream(Error);
pub stream: Stream,
base: *Stream,
// Right now the look-ahead space is statically allocated, but a version with dynamic allocation
// is not too difficult to derive from this.
buffer: [buffer_size]u8,
index: usize,
at_end: bool,
pub fn init(base: *Stream) Self {
return Self{
.base = base,
.buffer = undefined,
.index = 0,
.at_end = false,
.stream = Stream{ .readFn = readFn },
};
}
pub fn putBackByte(self: *Self, byte: u8) void {
self.buffer[self.index] = byte;
self.index += 1;
}
pub fn putBack(self: *Self, bytes: []const u8) void {
var pos = bytes.len;
while (pos != 0) {
pos -= 1;
self.putBackByte(bytes[pos]);
}
}
fn readFn(in_stream: *Stream, dest: []u8) Error!usize {
const self = @fieldParentPtr(Self, "stream", in_stream);
// copy over anything putBack()'d
var pos: usize = 0;
while (pos < dest.len and self.index != 0) {
dest[pos] = self.buffer[self.index - 1];
self.index -= 1;
pos += 1;
}
if (pos == dest.len or self.at_end) {
return pos;
}
// ask the backing stream for more
const left = dest.len - pos;
const read = try self.base.read(dest[pos..]);
assert(read <= left);
self.at_end = (read < left);
return pos + read;
}
};
}
pub const SliceInStream = struct {
const Self = this;
pub const Error = error { };
pub const Stream = InStream(Error);
pub stream: Stream,
pos: usize,
slice: []const u8,
pub fn init(slice: []const u8) Self {
return Self{
.slice = slice,
.pos = 0,
.stream = Stream{ .readFn = readFn },
};
}
fn readFn(in_stream: *Stream, dest: []u8) Error!usize {
const self = @fieldParentPtr(Self, "stream", in_stream);
const size = math.min(dest.len, self.slice.len - self.pos);
const end = self.pos + size;
mem.copy(u8, dest[0..size], self.slice[self.pos..end]);
self.pos = end;
return size;
}
};
/// This is a simple OutStream that writes to a slice, and returns an error
/// when it runs out of space.
pub const SliceOutStream = struct {
pub const Error = error{OutOfSpace};
pub const Stream = OutStream(Error);
pub stream: Stream,
pos: usize,
slice: []u8,
pub fn init(slice: []u8) SliceOutStream {
return SliceOutStream{
.slice = slice,
.pos = 0,
.stream = Stream{ .writeFn = writeFn },
};
}
pub fn getWritten(self: *const SliceOutStream) []const u8 {
return self.slice[0..self.pos];
}
pub fn reset(self: *SliceOutStream) void {
self.pos = 0;
}
fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
const self = @fieldParentPtr(SliceOutStream, "stream", out_stream);
assert(self.pos <= self.slice.len);
const n =
if (self.pos + bytes.len <= self.slice.len)
bytes.len
else
self.slice.len - self.pos;
std.mem.copy(u8, self.slice[self.pos..self.pos + n], bytes[0..n]);
self.pos += n;
if (n < bytes.len) {
return Error.OutOfSpace;
}
}
};
pub fn BufferedOutStream(comptime Error: type) type { pub fn BufferedOutStream(comptime Error: type) type {
return BufferedOutStreamCustom(os.page_size, Error); return BufferedOutStreamCustom(os.page_size, Error);
} }

View File

@ -2,6 +2,7 @@ const std = @import("index.zig");
const io = std.io; const io = std.io;
const DefaultPrng = std.rand.DefaultPrng; const DefaultPrng = std.rand.DefaultPrng;
const assert = std.debug.assert; const assert = std.debug.assert;
const assertError = std.debug.assertError;
const mem = std.mem; const mem = std.mem;
const os = std.os; const os = std.os;
const builtin = @import("builtin"); const builtin = @import("builtin");
@ -60,3 +61,74 @@ test "BufferOutStream" {
assert(mem.eql(u8, buffer.toSlice(), "x: 42\ny: 1234\n")); assert(mem.eql(u8, buffer.toSlice(), "x: 42\ny: 1234\n"));
} }
test "SliceInStream" {
const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7 };
var ss = io.SliceInStream.init(bytes);
var dest: [4]u8 = undefined;
var read = try ss.stream.read(dest[0..4]);
assert(read == 4);
assert(mem.eql(u8, dest[0..4], bytes[0..4]));
read = try ss.stream.read(dest[0..4]);
assert(read == 3);
assert(mem.eql(u8, dest[0..3], bytes[4..7]));
read = try ss.stream.read(dest[0..4]);
assert(read == 0);
}
test "PeekStream" {
const bytes = []const u8 { 1, 2, 3, 4, 5, 6, 7, 8 };
var ss = io.SliceInStream.init(bytes);
var ps = io.PeekStream(2, io.SliceInStream.Error).init(&ss.stream);
var dest: [4]u8 = undefined;
ps.putBackByte(9);
ps.putBackByte(10);
var read = try ps.stream.read(dest[0..4]);
assert(read == 4);
assert(dest[0] == 10);
assert(dest[1] == 9);
assert(mem.eql(u8, dest[2..4], bytes[0..2]));
read = try ps.stream.read(dest[0..4]);
assert(read == 4);
assert(mem.eql(u8, dest[0..4], bytes[2..6]));
read = try ps.stream.read(dest[0..4]);
assert(read == 2);
assert(mem.eql(u8, dest[0..2], bytes[6..8]));
ps.putBackByte(11);
ps.putBackByte(12);
read = try ps.stream.read(dest[0..4]);
assert(read == 2);
assert(dest[0] == 12);
assert(dest[1] == 11);
}
test "SliceOutStream" {
var buffer: [10]u8 = undefined;
var ss = io.SliceOutStream.init(buffer[0..]);
try ss.stream.write("Hello");
assert(mem.eql(u8, ss.getWritten(), "Hello"));
try ss.stream.write("world");
assert(mem.eql(u8, ss.getWritten(), "Helloworld"));
assertError(ss.stream.write("!"), error.OutOfSpace);
assert(mem.eql(u8, ss.getWritten(), "Helloworld"));
ss.reset();
assert(ss.getWritten().len == 0);
assertError(ss.stream.write("Hello world!"), error.OutOfSpace);
assert(mem.eql(u8, ss.getWritten(), "Hello worl"));
}

View File

@ -16,6 +16,7 @@ comptime {
_ = @import("cases/bugs/828.zig"); _ = @import("cases/bugs/828.zig");
_ = @import("cases/bugs/920.zig"); _ = @import("cases/bugs/920.zig");
_ = @import("cases/byval_arg_var.zig"); _ = @import("cases/byval_arg_var.zig");
_ = @import("cases/cancel.zig");
_ = @import("cases/cast.zig"); _ = @import("cases/cast.zig");
_ = @import("cases/const_slice_child.zig"); _ = @import("cases/const_slice_child.zig");
_ = @import("cases/coroutine_await_struct.zig"); _ = @import("cases/coroutine_await_struct.zig");

92
test/cases/cancel.zig Normal file
View File

@ -0,0 +1,92 @@
const std = @import("std");
var defer_f1: bool = false;
var defer_f2: bool = false;
var defer_f3: bool = false;
test "cancel forwards" {
var da = std.heap.DirectAllocator.init();
defer da.deinit();
const p = async<&da.allocator> f1() catch unreachable;
cancel p;
std.debug.assert(defer_f1);
std.debug.assert(defer_f2);
std.debug.assert(defer_f3);
}
async fn f1() void {
defer {
defer_f1 = true;
}
await (async f2() catch unreachable);
}
async fn f2() void {
defer {
defer_f2 = true;
}
await (async f3() catch unreachable);
}
async fn f3() void {
defer {
defer_f3 = true;
}
suspend;
}
var defer_b1: bool = false;
var defer_b2: bool = false;
var defer_b3: bool = false;
var defer_b4: bool = false;
test "cancel backwards" {
var da = std.heap.DirectAllocator.init();
defer da.deinit();
const p = async<&da.allocator> b1() catch unreachable;
cancel p;
std.debug.assert(defer_b1);
std.debug.assert(defer_b2);
std.debug.assert(defer_b3);
std.debug.assert(defer_b4);
}
async fn b1() void {
defer {
defer_b1 = true;
}
await (async b2() catch unreachable);
}
var b4_handle: promise = undefined;
async fn b2() void {
const b3_handle = async b3() catch unreachable;
resume b4_handle;
cancel b4_handle;
defer {
defer_b2 = true;
}
const value = await b3_handle;
@panic("unreachable");
}
async fn b3() i32 {
defer {
defer_b3 = true;
}
await (async b4() catch unreachable);
return 1234;
}
async fn b4() void {
defer {
defer_b4 = true;
}
suspend |p| {
b4_handle = p;
}
suspend;
}

View File

@ -244,8 +244,8 @@ test "break from suspend" {
std.debug.assert(my_result == 2); std.debug.assert(my_result == 2);
} }
async fn testBreakFromSuspend(my_result: *i32) void { async fn testBreakFromSuspend(my_result: *i32) void {
s: suspend |p| { suspend |p| {
break :s; resume p;
} }
my_result.* += 1; my_result.* += 1;
suspend; suspend;

View File

@ -1,6 +1,64 @@
const tests = @import("tests.zig"); const tests = @import("tests.zig");
pub fn addCases(cases: *tests.CompileErrorContext) void { pub fn addCases(cases: *tests.CompileErrorContext) void {
cases.add(
"while loop body expression ignored",
\\fn returns() usize {
\\ return 2;
\\}
\\export fn f1() void {
\\ while (true) returns();
\\}
\\export fn f2() void {
\\ var x: ?i32 = null;
\\ while (x) |_| returns();
\\}
\\export fn f3() void {
\\ var x: error!i32 = error.Bad;
\\ while (x) |_| returns() else |_| unreachable;
\\}
,
".tmp_source.zig:5:25: error: expression value is ignored",
".tmp_source.zig:9:26: error: expression value is ignored",
".tmp_source.zig:13:26: error: expression value is ignored",
);
cases.add(
"missing parameter name of generic function",
\\fn dump(var) void {}
\\export fn entry() void {
\\ var a: u8 = 9;
\\ dump(a);
\\}
,
".tmp_source.zig:1:9: error: missing parameter name",
);
cases.add(
"non-inline for loop on a type that requires comptime",
\\const Foo = struct {
\\ name: []const u8,
\\ T: type,
\\};
\\export fn entry() void {
\\ const xx: [2]Foo = undefined;
\\ for (xx) |f| {}
\\}
,
".tmp_source.zig:7:15: error: variable of type 'Foo' must be const or comptime",
);
cases.add(
"generic fn as parameter without comptime keyword",
\\fn f(_: fn (var) void) void {}
\\fn g(_: var) void {}
\\export fn entry() void {
\\ f(g);
\\}
,
".tmp_source.zig:1:9: error: parameter of type 'fn(var)var' must be declared comptime",
);
cases.add( cases.add(
"optional pointer to void in extern struct", "optional pointer to void in extern struct",
\\comptime { \\comptime {

View File

@ -2,6 +2,7 @@ const std = @import("std");
const TestContext = @import("../../src-self-hosted/test.zig").TestContext; const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
pub fn addCases(ctx: *TestContext) !void { pub fn addCases(ctx: *TestContext) !void {
// hello world
try ctx.testCompareOutputLibC( try ctx.testCompareOutputLibC(
\\extern fn puts([*]const u8) void; \\extern fn puts([*]const u8) void;
\\export fn main() c_int { \\export fn main() c_int {
@ -9,4 +10,16 @@ pub fn addCases(ctx: *TestContext) !void {
\\ return 0; \\ return 0;
\\} \\}
, "Hello, world!" ++ std.cstr.line_sep); , "Hello, world!" ++ std.cstr.line_sep);
// function calling another function
try ctx.testCompareOutputLibC(
\\extern fn puts(s: [*]const u8) void;
\\export fn main() c_int {
\\ return foo(c"OK");
\\}
\\fn foo(s: [*]const u8) c_int {
\\ puts(s);
\\ return 0;
\\}
, "OK" ++ std.cstr.line_sep);
} }