mirror of
https://github.com/ziglang/zig.git
synced 2026-01-09 17:05:16 +00:00
this also deletes C string literals from the language, and then makes the std lib changes and compiler changes necessary to get the behavior tests and std lib tests passing again.
589 lines
20 KiB
Zig
589 lines
20 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const Scope = @import("scope.zig").Scope;
|
|
const Compilation = @import("compilation.zig").Compilation;
|
|
const ObjectFile = @import("codegen.zig").ObjectFile;
|
|
const llvm = @import("llvm.zig");
|
|
const Buffer = std.Buffer;
|
|
const assert = std.debug.assert;
|
|
|
|
/// Values are ref-counted, heap-allocated, and copy-on-write
|
|
/// If there is only 1 ref then write need not copy
|
|
pub const Value = struct {
|
|
id: Id,
|
|
typ: *Type,
|
|
ref_count: std.atomic.Int(usize),
|
|
|
|
/// Thread-safe
|
|
pub fn ref(base: *Value) void {
|
|
_ = base.ref_count.incr();
|
|
}
|
|
|
|
/// Thread-safe
|
|
pub fn deref(base: *Value, comp: *Compilation) void {
|
|
if (base.ref_count.decr() == 1) {
|
|
base.typ.base.deref(comp);
|
|
switch (base.id) {
|
|
Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp),
|
|
Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
|
|
Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp),
|
|
Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp),
|
|
Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
|
|
Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
|
|
Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp),
|
|
Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp),
|
|
Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void {
|
|
base.typ.base.deref(comp);
|
|
new_type.base.ref();
|
|
base.typ = new_type;
|
|
}
|
|
|
|
pub fn getRef(base: *Value) *Value {
|
|
base.ref();
|
|
return base;
|
|
}
|
|
|
|
pub fn cast(base: *Value, comptime T: type) ?*T {
|
|
if (base.id != @field(Id, @typeName(T))) return null;
|
|
return @fieldParentPtr(T, "base", base);
|
|
}
|
|
|
|
pub fn dump(base: *const Value) void {
|
|
std.debug.warn("{}", @tagName(base.id));
|
|
}
|
|
|
|
pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?*llvm.Value) {
|
|
switch (base.id) {
|
|
Id.Type => unreachable,
|
|
Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile),
|
|
Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
|
|
Id.Void => return null,
|
|
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
|
|
Id.NoReturn => unreachable,
|
|
Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile),
|
|
Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile),
|
|
Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile),
|
|
}
|
|
}
|
|
|
|
pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
|
|
if (self.ref_count.get() == 1) {
|
|
// ( ͡° ͜ʖ ͡°)
|
|
return self;
|
|
}
|
|
|
|
assert(self.ref_count.decr() != 1);
|
|
return self.copy(comp);
|
|
}
|
|
|
|
pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
|
|
switch (base.id) {
|
|
Id.Type => unreachable,
|
|
Id.Fn => unreachable,
|
|
Id.FnProto => unreachable,
|
|
Id.Void => unreachable,
|
|
Id.Bool => unreachable,
|
|
Id.NoReturn => unreachable,
|
|
Id.Ptr => unreachable,
|
|
Id.Array => unreachable,
|
|
Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base,
|
|
}
|
|
}
|
|
|
|
pub const Parent = union(enum) {
|
|
None,
|
|
BaseStruct: BaseStruct,
|
|
BaseArray: BaseArray,
|
|
BaseUnion: *Value,
|
|
BaseScalar: *Value,
|
|
|
|
pub const BaseStruct = struct {
|
|
val: *Value,
|
|
field_index: usize,
|
|
};
|
|
|
|
pub const BaseArray = struct {
|
|
val: *Value,
|
|
elem_index: usize,
|
|
};
|
|
};
|
|
|
|
pub const Id = enum {
|
|
Type,
|
|
Fn,
|
|
Void,
|
|
Bool,
|
|
NoReturn,
|
|
Array,
|
|
Ptr,
|
|
Int,
|
|
FnProto,
|
|
};
|
|
|
|
pub const Type = @import("type.zig").Type;
|
|
|
|
pub const FnProto = struct {
|
|
base: Value,
|
|
|
|
/// The main external name that is used in the .o file.
|
|
/// TODO https://github.com/ziglang/zig/issues/265
|
|
symbol_name: Buffer,
|
|
|
|
pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto {
|
|
const self = try comp.gpa().create(FnProto);
|
|
self.* = FnProto{
|
|
.base = Value{
|
|
.id = Value.Id.FnProto,
|
|
.typ = &fn_type.base,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.symbol_name = symbol_name,
|
|
};
|
|
fn_type.base.base.ref();
|
|
return self;
|
|
}
|
|
|
|
pub fn destroy(self: *FnProto, comp: *Compilation) void {
|
|
self.symbol_name.deinit();
|
|
comp.gpa().destroy(self);
|
|
}
|
|
|
|
pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?*llvm.Value {
|
|
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 Fn = struct {
|
|
base: Value,
|
|
|
|
/// The main external name that is used in the .o file.
|
|
/// TODO https://github.com/ziglang/zig/issues/265
|
|
symbol_name: Buffer,
|
|
|
|
/// parent should be the top level decls or container decls
|
|
fndef_scope: *Scope.FnDef,
|
|
|
|
/// parent is scope for last parameter
|
|
child_scope: *Scope,
|
|
|
|
/// parent is child_scope
|
|
block_scope: ?*Scope.Block,
|
|
|
|
/// Path to the object file that contains this function
|
|
containing_object: Buffer,
|
|
|
|
link_set_node: *std.TailQueue(?*Value.Fn).Node,
|
|
|
|
/// Creates a Fn value with 1 ref
|
|
/// Takes ownership of symbol_name
|
|
pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: Buffer) !*Fn {
|
|
const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node);
|
|
link_set_node.* = Compilation.FnLinkSet.Node{
|
|
.data = null,
|
|
.next = undefined,
|
|
.prev = undefined,
|
|
};
|
|
errdefer comp.gpa().destroy(link_set_node);
|
|
|
|
const self = try comp.gpa().create(Fn);
|
|
self.* = Fn{
|
|
.base = Value{
|
|
.id = Value.Id.Fn,
|
|
.typ = &fn_type.base,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.fndef_scope = fndef_scope,
|
|
.child_scope = &fndef_scope.base,
|
|
.block_scope = null,
|
|
.symbol_name = symbol_name,
|
|
.containing_object = Buffer.initNull(comp.gpa()),
|
|
.link_set_node = link_set_node,
|
|
};
|
|
fn_type.base.base.ref();
|
|
fndef_scope.fn_val = self;
|
|
fndef_scope.base.ref();
|
|
return self;
|
|
}
|
|
|
|
pub fn destroy(self: *Fn, comp: *Compilation) void {
|
|
// remove with a tombstone so that we do not have to grab a lock
|
|
if (self.link_set_node.data != null) {
|
|
// it's now the job of the link step to find this tombstone and
|
|
// deallocate it.
|
|
self.link_set_node.data = null;
|
|
} else {
|
|
comp.gpa().destroy(self.link_set_node);
|
|
}
|
|
|
|
self.containing_object.deinit();
|
|
self.fndef_scope.base.deref(comp);
|
|
self.symbol_name.deinit();
|
|
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.Value {
|
|
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 {
|
|
base: Value,
|
|
|
|
pub fn get(comp: *Compilation) *Void {
|
|
comp.void_value.base.ref();
|
|
return comp.void_value;
|
|
}
|
|
|
|
pub fn destroy(self: *Void, comp: *Compilation) void {
|
|
comp.gpa().destroy(self);
|
|
}
|
|
};
|
|
|
|
pub const Bool = struct {
|
|
base: Value,
|
|
x: bool,
|
|
|
|
pub fn get(comp: *Compilation, x: bool) *Bool {
|
|
if (x) {
|
|
comp.true_value.base.ref();
|
|
return comp.true_value;
|
|
} else {
|
|
comp.false_value.base.ref();
|
|
return comp.false_value;
|
|
}
|
|
}
|
|
|
|
pub fn destroy(self: *Bool, comp: *Compilation) void {
|
|
comp.gpa().destroy(self);
|
|
}
|
|
|
|
pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) !?*llvm.Value {
|
|
const llvm_type = llvm.Int1TypeInContext(ofile.context) orelse return error.OutOfMemory;
|
|
if (self.x) {
|
|
return llvm.ConstAllOnes(llvm_type);
|
|
} else {
|
|
return llvm.ConstNull(llvm_type);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const NoReturn = struct {
|
|
base: Value,
|
|
|
|
pub fn get(comp: *Compilation) *NoReturn {
|
|
comp.noreturn_value.base.ref();
|
|
return comp.noreturn_value;
|
|
}
|
|
|
|
pub fn destroy(self: *NoReturn, comp: *Compilation) void {
|
|
comp.gpa().destroy(self);
|
|
}
|
|
};
|
|
|
|
pub const Ptr = struct {
|
|
base: Value,
|
|
special: Special,
|
|
mut: Mut,
|
|
|
|
pub const Mut = enum {
|
|
CompTimeConst,
|
|
CompTimeVar,
|
|
RunTime,
|
|
};
|
|
|
|
pub const Special = union(enum) {
|
|
Scalar: *Value,
|
|
BaseArray: BaseArray,
|
|
BaseStruct: BaseStruct,
|
|
HardCodedAddr: u64,
|
|
Discard,
|
|
};
|
|
|
|
pub const BaseArray = struct {
|
|
val: *Value,
|
|
elem_index: usize,
|
|
};
|
|
|
|
pub const BaseStruct = struct {
|
|
val: *Value,
|
|
field_index: usize,
|
|
};
|
|
|
|
pub async fn createArrayElemPtr(
|
|
comp: *Compilation,
|
|
array_val: *Array,
|
|
mut: Type.Pointer.Mut,
|
|
size: Type.Pointer.Size,
|
|
elem_index: usize,
|
|
) !*Ptr {
|
|
array_val.base.ref();
|
|
errdefer array_val.base.deref(comp);
|
|
|
|
const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type;
|
|
const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{
|
|
.child_type = elem_type,
|
|
.mut = mut,
|
|
.vol = Type.Pointer.Vol.Non,
|
|
.size = size,
|
|
.alignment = Type.Pointer.Align.Abi,
|
|
}) catch unreachable);
|
|
var ptr_type_consumed = false;
|
|
errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp);
|
|
|
|
const self = try comp.gpa().create(Value.Ptr);
|
|
self.* = Value.Ptr{
|
|
.base = Value{
|
|
.id = Value.Id.Ptr,
|
|
.typ = &ptr_type.base,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.special = Special{
|
|
.BaseArray = BaseArray{
|
|
.val = &array_val.base,
|
|
.elem_index = 0,
|
|
},
|
|
},
|
|
.mut = Mut.CompTimeConst,
|
|
};
|
|
ptr_type_consumed = true;
|
|
errdefer comp.gpa().destroy(self);
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn destroy(self: *Ptr, comp: *Compilation) void {
|
|
comp.gpa().destroy(self);
|
|
}
|
|
|
|
pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?*llvm.Value {
|
|
const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context);
|
|
// TODO carefully port the logic from codegen.cpp:gen_const_val_ptr
|
|
switch (self.special) {
|
|
Special.Scalar => |scalar| @panic("TODO"),
|
|
Special.BaseArray => |base_array| {
|
|
// TODO put this in one .o file only, and after that, generate extern references to it
|
|
const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?;
|
|
const ptr_bit_count = ofile.comp.target_ptr_bits;
|
|
const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory;
|
|
const indices = [_]*llvm.Value{
|
|
llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory,
|
|
llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory,
|
|
};
|
|
return llvm.ConstInBoundsGEP(
|
|
array_llvm_value,
|
|
&indices,
|
|
@intCast(c_uint, indices.len),
|
|
) orelse return error.OutOfMemory;
|
|
},
|
|
Special.BaseStruct => |base_struct| @panic("TODO"),
|
|
Special.HardCodedAddr => |addr| @panic("TODO"),
|
|
Special.Discard => unreachable,
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Array = struct {
|
|
base: Value,
|
|
special: Special,
|
|
|
|
pub const Special = union(enum) {
|
|
Undefined,
|
|
OwnedBuffer: []u8,
|
|
Explicit: Data,
|
|
};
|
|
|
|
pub const Data = struct {
|
|
parent: Parent,
|
|
elements: []*Value,
|
|
};
|
|
|
|
/// Takes ownership of buffer
|
|
pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array {
|
|
const u8_type = Type.Int.get_u8(comp);
|
|
defer u8_type.base.base.deref(comp);
|
|
|
|
const array_type = try await (async Type.Array.get(comp, Type.Array.Key{
|
|
.elem_type = &u8_type.base,
|
|
.len = buffer.len,
|
|
}) catch unreachable);
|
|
errdefer array_type.base.base.deref(comp);
|
|
|
|
const self = try comp.gpa().create(Value.Array);
|
|
self.* = Value.Array{
|
|
.base = Value{
|
|
.id = Value.Id.Array,
|
|
.typ = &array_type.base,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.special = Special{ .OwnedBuffer = buffer },
|
|
};
|
|
errdefer comp.gpa().destroy(self);
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn destroy(self: *Array, comp: *Compilation) void {
|
|
switch (self.special) {
|
|
Special.Undefined => {},
|
|
Special.OwnedBuffer => |buf| {
|
|
comp.gpa().free(buf);
|
|
},
|
|
Special.Explicit => {},
|
|
}
|
|
comp.gpa().destroy(self);
|
|
}
|
|
|
|
pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?*llvm.Value {
|
|
switch (self.special) {
|
|
Special.Undefined => {
|
|
const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
|
|
return llvm.GetUndef(llvm_type);
|
|
},
|
|
Special.OwnedBuffer => |buf| {
|
|
const dont_null_terminate = 1;
|
|
const llvm_str_init = llvm.ConstStringInContext(
|
|
ofile.context,
|
|
buf.ptr,
|
|
@intCast(c_uint, buf.len),
|
|
dont_null_terminate,
|
|
) orelse return error.OutOfMemory;
|
|
const str_init_type = llvm.TypeOf(llvm_str_init);
|
|
const global = llvm.AddGlobal(ofile.module, str_init_type, "") orelse return error.OutOfMemory;
|
|
llvm.SetInitializer(global, llvm_str_init);
|
|
llvm.SetLinkage(global, llvm.PrivateLinkage);
|
|
llvm.SetGlobalConstant(global, 1);
|
|
llvm.SetUnnamedAddr(global, 1);
|
|
llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type));
|
|
return global;
|
|
},
|
|
Special.Explicit => @panic("TODO"),
|
|
}
|
|
|
|
//{
|
|
// uint64_t len = type_entry->data.array.len;
|
|
// if (const_val->data.x_array.special == ConstArraySpecialUndef) {
|
|
// return LLVMGetUndef(type_entry->type_ref);
|
|
// }
|
|
|
|
// LLVMValueRef *values = allocate<LLVMValueRef>(len);
|
|
// LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref;
|
|
// bool make_unnamed_struct = false;
|
|
// for (uint64_t i = 0; i < len; i += 1) {
|
|
// ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i];
|
|
// LLVMValueRef val = gen_const_val(g, elem_value, "");
|
|
// values[i] = val;
|
|
// make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val);
|
|
// }
|
|
// if (make_unnamed_struct) {
|
|
// return LLVMConstStruct(values, len, true);
|
|
// } else {
|
|
// return LLVMConstArray(element_type_ref, values, (unsigned)len);
|
|
// }
|
|
//}
|
|
}
|
|
};
|
|
|
|
pub const Int = struct {
|
|
base: Value,
|
|
big_int: std.math.big.Int,
|
|
|
|
pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int {
|
|
const self = try comp.gpa().create(Value.Int);
|
|
self.* = Value.Int{
|
|
.base = Value{
|
|
.id = Value.Id.Int,
|
|
.typ = typ,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.big_int = undefined,
|
|
};
|
|
typ.base.ref();
|
|
errdefer comp.gpa().destroy(self);
|
|
|
|
self.big_int = try std.math.big.Int.init(comp.gpa());
|
|
errdefer self.big_int.deinit();
|
|
|
|
try self.big_int.setString(base, value);
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?*llvm.Value {
|
|
switch (self.base.typ.id) {
|
|
Type.Id.Int => {
|
|
const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
|
|
if (self.big_int.len() == 0) {
|
|
return llvm.ConstNull(type_ref);
|
|
}
|
|
const unsigned_val = if (self.big_int.len() == 1) blk: {
|
|
break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false));
|
|
} else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: {
|
|
break :blk llvm.ConstIntOfArbitraryPrecision(
|
|
type_ref,
|
|
@intCast(c_uint, self.big_int.len()),
|
|
@ptrCast([*]u64, self.big_int.limbs.ptr),
|
|
);
|
|
} else {
|
|
@compileError("std.math.Big.Int.Limb size does not match LLVM");
|
|
};
|
|
return if (self.big_int.isPositive()) unsigned_val else llvm.ConstNeg(unsigned_val);
|
|
},
|
|
Type.Id.ComptimeInt => unreachable,
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
pub fn copy(old: *Int, comp: *Compilation) !*Int {
|
|
old.base.typ.base.ref();
|
|
errdefer old.base.typ.base.deref(comp);
|
|
|
|
const new = try comp.gpa().create(Value.Int);
|
|
new.* = Value.Int{
|
|
.base = Value{
|
|
.id = Value.Id.Int,
|
|
.typ = old.base.typ,
|
|
.ref_count = std.atomic.Int(usize).init(1),
|
|
},
|
|
.big_int = undefined,
|
|
};
|
|
errdefer comp.gpa().destroy(new);
|
|
|
|
new.big_int = try old.big_int.clone();
|
|
errdefer new.big_int.deinit();
|
|
|
|
return new;
|
|
}
|
|
|
|
pub fn destroy(self: *Int, comp: *Compilation) void {
|
|
self.big_int.deinit();
|
|
comp.gpa().destroy(self);
|
|
}
|
|
};
|
|
};
|