aboutsummaryrefslogtreecommitdiff
path: root/src-self-hosted
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2020-05-01 17:35:52 -0400
committerGitHub <noreply@github.com>2020-05-01 17:35:52 -0400
commit3386bb896d071eef4ff571fac399e18b2270a382 (patch)
treec3e597506a6f5a41269acdd386fd87bd473cdaa9 /src-self-hosted
parent94b0d0e80242563f4ad7ad41e3c0f5193a60b70c (diff)
parentec6ef86219578822fd32bbe2e5eb83b24ddfdca6 (diff)
downloadzig-3386bb896d071eef4ff571fac399e18b2270a382.tar.gz
zig-3386bb896d071eef4ff571fac399e18b2270a382.zip
Merge pull request #5192 from ziglang/stage2-tests
add ZIR compare output test case to test suite
Diffstat (limited to 'src-self-hosted')
-rw-r--r--src-self-hosted/codegen.zig55
-rw-r--r--src-self-hosted/ir.zig839
-rw-r--r--src-self-hosted/ir/text.zig422
-rw-r--r--src-self-hosted/link.zig75
-rw-r--r--src-self-hosted/test.zig407
-rw-r--r--src-self-hosted/translate_c.zig30
-rw-r--r--src-self-hosted/type.zig677
-rw-r--r--src-self-hosted/value.zig416
8 files changed, 2133 insertions, 788 deletions
diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig
index 3a8d0e1282..675b8faad2 100644
--- a/src-self-hosted/codegen.zig
+++ b/src-self-hosted/codegen.zig
@@ -39,7 +39,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.
defer function.inst_table.deinit();
defer function.errors.deinit();
- for (module_fn.body) |inst| {
+ for (module_fn.body.instructions) |inst| {
const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
error.CodegenFail => {
assert(function.errors.items.len != 0);
@@ -77,32 +77,63 @@ const Function = struct {
fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
- .unreach => return self.genPanic(inst.src),
+ .breakpoint => return self.genBreakpoint(inst.src),
+ .unreach => return MCValue{ .unreach = {} },
.constant => unreachable, // excluded from function bodies
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
.ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?),
.bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?),
+ .ret => return self.genRet(inst.cast(ir.Inst.Ret).?),
+ .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?),
+ .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?),
+ .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?),
+ .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?),
}
}
- fn genPanic(self: *Function, src: usize) !MCValue {
- // TODO change this to call the panic function
+ fn genBreakpoint(self: *Function, src: usize) !MCValue {
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xcc); // int3
},
- else => return self.fail(src, "TODO implement panic for {}", .{self.module.target.cpu.arch}),
+ else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}),
}
return .unreach;
}
- fn genRet(self: *Function, src: usize) !void {
- // TODO change this to call the panic function
+ fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue {
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xc3); // ret
},
- else => return self.fail(src, "TODO implement ret for {}", .{self.module.target.cpu.arch}),
+ else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}),
+ }
+ return .unreach;
+ }
+
+ fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue {
+ switch (self.module.target.cpu.arch) {
+ else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}),
+ }
+ }
+
+ fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue {
+ switch (self.module.target.cpu.arch) {
+ else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}),
+ }
+ }
+
+ fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue {
+ switch (self.module.target.cpu.arch) {
+ else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}),
+ }
+ }
+
+ fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue {
+ // Here you can specialize this instruction if it makes sense to, otherwise the default
+ // will call genIsNull and invert the result.
+ switch (self.module.target.cpu.arch) {
+ else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}),
}
}
@@ -501,11 +532,19 @@ fn Reg(comptime arch: Target.Cpu.Arch) type {
bh,
ch,
dh,
+ bph,
+ sph,
+ sih,
+ dih,
al,
bl,
cl,
dl,
+ bpl,
+ spl,
+ sil,
+ dil,
r8b,
r9b,
r10b,
diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig
index 310591e629..6e58236ca8 100644
--- a/src-self-hosted/ir.zig
+++ b/src-self-hosted/ir.zig
@@ -4,10 +4,12 @@ const Allocator = std.mem.Allocator;
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const assert = std.debug.assert;
-const text = @import("ir/text.zig");
-const BigInt = std.math.big.Int;
+const BigIntConst = std.math.big.int.Const;
+const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
+pub const text = @import("ir/text.zig");
+
/// These are in-memory, analyzed instructions. See `text.Inst` for the representation
/// of instructions that correspond to the ZIR text format.
/// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
@@ -20,11 +22,17 @@ pub const Inst = struct {
src: usize,
pub const Tag = enum {
- unreach,
- constant,
assembly,
- ptrtoint,
bitcast,
+ breakpoint,
+ cmp,
+ condbr,
+ constant,
+ isnonnull,
+ isnull,
+ ptrtoint,
+ ret,
+ unreach,
};
pub fn cast(base: *Inst, comptime T: type) ?*T {
@@ -40,23 +48,64 @@ pub const Inst = struct {
/// Returns `null` if runtime-known.
pub fn value(base: *Inst) ?Value {
- return switch (base.tag) {
- .unreach => Value.initTag(.noreturn_value),
- .constant => base.cast(Constant).?.val,
-
- .assembly,
- .ptrtoint,
- .bitcast,
- => null,
- };
+ if (base.ty.onePossibleValue())
+ return Value.initTag(.the_one_possible_value);
+
+ const inst = base.cast(Constant) orelse return null;
+ return inst.val;
}
- pub const Unreach = struct {
- pub const base_tag = Tag.unreach;
+ pub const Assembly = struct {
+ pub const base_tag = Tag.assembly;
+ base: Inst,
+
+ args: struct {
+ asm_source: []const u8,
+ is_volatile: bool,
+ output: ?[]const u8,
+ inputs: []const []const u8,
+ clobbers: []const []const u8,
+ args: []const *Inst,
+ },
+ };
+
+ pub const BitCast = struct {
+ pub const base_tag = Tag.bitcast;
+
+ base: Inst,
+ args: struct {
+ operand: *Inst,
+ },
+ };
+
+ pub const Breakpoint = struct {
+ pub const base_tag = Tag.breakpoint;
base: Inst,
args: void,
};
+ pub const Cmp = struct {
+ pub const base_tag = Tag.cmp;
+
+ base: Inst,
+ args: struct {
+ lhs: *Inst,
+ op: std.math.CompareOperator,
+ rhs: *Inst,
+ },
+ };
+
+ pub const CondBr = struct {
+ pub const base_tag = Tag.condbr;
+
+ base: Inst,
+ args: struct {
+ condition: *Inst,
+ true_body: Module.Body,
+ false_body: Module.Body,
+ },
+ };
+
pub const Constant = struct {
pub const base_tag = Tag.constant;
base: Inst,
@@ -64,17 +113,21 @@ pub const Inst = struct {
val: Value,
};
- pub const Assembly = struct {
- pub const base_tag = Tag.assembly;
+ pub const IsNonNull = struct {
+ pub const base_tag = Tag.isnonnull;
+
base: Inst,
+ args: struct {
+ operand: *Inst,
+ },
+ };
+ pub const IsNull = struct {
+ pub const base_tag = Tag.isnull;
+
+ base: Inst,
args: struct {
- asm_source: []const u8,
- is_volatile: bool,
- output: ?[]const u8,
- inputs: []const []const u8,
- clobbers: []const []const u8,
- args: []const *Inst,
+ operand: *Inst,
},
};
@@ -87,13 +140,16 @@ pub const Inst = struct {
},
};
- pub const BitCast = struct {
- pub const base_tag = Tag.bitcast;
+ pub const Ret = struct {
+ pub const base_tag = Tag.ret;
+ base: Inst,
+ args: void,
+ };
+ pub const Unreach = struct {
+ pub const base_tag = Tag.unreach;
base: Inst,
- args: struct {
- operand: *Inst,
- },
+ args: void,
};
};
@@ -108,6 +164,10 @@ pub const Module = struct {
arena: std.heap.ArenaAllocator,
fns: []Fn,
target: Target,
+ link_mode: std.builtin.LinkMode,
+ output_mode: std.builtin.OutputMode,
+ object_format: std.Target.ObjectFormat,
+ optimize_mode: std.builtin.Mode,
pub const Export = struct {
name: []const u8,
@@ -117,13 +177,21 @@ pub const Module = struct {
pub const Fn = struct {
analysis_status: enum { in_progress, failure, success },
- body: []*Inst,
+ body: Body,
fn_type: Type,
};
+ pub const Body = struct {
+ instructions: []*Inst,
+ };
+
pub fn deinit(self: *Module, allocator: *Allocator) void {
allocator.free(self.exports);
allocator.free(self.errors);
+ for (self.fns) |f| {
+ allocator.free(f.body.instructions);
+ }
+ allocator.free(self.fns);
self.arena.deinit();
self.* = undefined;
}
@@ -134,7 +202,15 @@ pub const ErrorMsg = struct {
msg: []const u8,
};
-pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !Module {
+pub const AnalyzeOptions = struct {
+ target: Target,
+ output_mode: std.builtin.OutputMode,
+ link_mode: std.builtin.LinkMode,
+ object_format: ?std.Target.ObjectFormat = null,
+ optimize_mode: std.builtin.Mode,
+};
+
+pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module {
var ctx = Analyze{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
@@ -143,7 +219,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
.decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
.exports = std.ArrayList(Module.Export).init(allocator),
.fns = std.ArrayList(Module.Fn).init(allocator),
- .target = target,
+ .target = options.target,
+ .optimize_mode = options.optimize_mode,
+ .link_mode = options.link_mode,
+ .output_mode = options.output_mode,
};
defer ctx.errors.deinit();
defer ctx.decl_table.deinit();
@@ -162,7 +241,11 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, target: Target) !
.errors = ctx.errors.toOwnedSlice(),
.fns = ctx.fns.toOwnedSlice(),
.arena = ctx.arena,
- .target = target,
+ .target = ctx.target,
+ .link_mode = ctx.link_mode,
+ .output_mode = ctx.output_mode,
+ .object_format = options.object_format orelse ctx.target.getObjectFormat(),
+ .optimize_mode = ctx.optimize_mode,
};
}
@@ -175,6 +258,9 @@ const Analyze = struct {
exports: std.ArrayList(Module.Export),
fns: std.ArrayList(Module.Fn),
target: Target,
+ link_mode: std.builtin.LinkMode,
+ optimize_mode: std.builtin.Mode,
+ output_mode: std.builtin.OutputMode,
const NewDecl = struct {
/// null means a semantic analysis error happened
@@ -187,10 +273,15 @@ const Analyze = struct {
};
const Fn = struct {
- body: std.ArrayList(*Inst),
- inst_table: std.AutoHashMap(*text.Inst, NewInst),
/// Index into Module fns array
fn_index: usize,
+ inner_block: Block,
+ inst_table: std.AutoHashMap(*text.Inst, NewInst),
+ };
+
+ const Block = struct {
+ func: *Fn,
+ instructions: std.ArrayList(*Inst),
};
const InnerError = error{ OutOfMemory, AnalysisFail };
@@ -203,9 +294,9 @@ const Analyze = struct {
}
}
- fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst {
- if (opt_func) |func| {
- if (func.inst_table.get(old_inst)) |kv| {
+ fn resolveInst(self: *Analyze, opt_block: ?*Block, old_inst: *text.Inst) InnerError!*Inst {
+ if (opt_block) |block| {
+ if (block.func.inst_table.get(old_inst)) |kv| {
return kv.value.ptr orelse return error.AnalysisFail;
}
}
@@ -225,12 +316,12 @@ const Analyze = struct {
}
}
- fn requireFunctionBody(self: *Analyze, func: ?*Fn, src: usize) !*Fn {
- return func orelse return self.fail(src, "instruction illegal outside function body", .{});
+ fn requireRuntimeBlock(self: *Analyze, block: ?*Block, src: usize) !*Block {
+ return block orelse return self.fail(src, "instruction illegal outside function body", .{});
}
- fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue {
- const new_inst = try self.resolveInst(func, old_inst);
+ fn resolveInstConst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!TypedValue {
+ const new_inst = try self.resolveInst(block, old_inst);
const val = try self.resolveConstValue(new_inst);
return TypedValue{
.ty = new_inst.ty,
@@ -239,28 +330,39 @@ const Analyze = struct {
}
fn resolveConstValue(self: *Analyze, base: *Inst) !Value {
- return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{});
+ return (try self.resolveDefinedValue(base)) orelse
+ return self.fail(base.src, "unable to resolve comptime value", .{});
}
- fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 {
- const new_inst = try self.resolveInst(func, old_inst);
+ fn resolveDefinedValue(self: *Analyze, base: *Inst) !?Value {
+ if (base.value()) |val| {
+ if (val.isUndef()) {
+ return self.fail(base.src, "use of undefined value here causes undefined behavior", .{});
+ }
+ return val;
+ }
+ return null;
+ }
+
+ fn resolveConstString(self: *Analyze, block: ?*Block, old_inst: *text.Inst) ![]u8 {
+ const new_inst = try self.resolveInst(block, old_inst);
const wanted_type = Type.initTag(.const_slice_u8);
- const coerced_inst = try self.coerce(func, wanted_type, new_inst);
+ const coerced_inst = try self.coerce(block, wanted_type, new_inst);
const val = try self.resolveConstValue(coerced_inst);
return val.toAllocatedBytes(&self.arena.allocator);
}
- fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type {
- const new_inst = try self.resolveInst(func, old_inst);
+ fn resolveType(self: *Analyze, block: ?*Block, old_inst: *text.Inst) !Type {
+ const new_inst = try self.resolveInst(block, old_inst);
const wanted_type = Type.initTag(.@"type");
- const coerced_inst = try self.coerce(func, wanted_type, new_inst);
+ const coerced_inst = try self.coerce(block, wanted_type, new_inst);
const val = try self.resolveConstValue(coerced_inst);
return val.toType();
}
- fn analyzeExport(self: *Analyze, func: ?*Fn, export_inst: *text.Inst.Export) !void {
- const symbol_name = try self.resolveConstString(func, export_inst.positionals.symbol_name);
- const typed_value = try self.resolveInstConst(func, export_inst.positionals.value);
+ fn analyzeExport(self: *Analyze, block: ?*Block, export_inst: *text.Inst.Export) !void {
+ const symbol_name = try self.resolveConstString(block, export_inst.positionals.symbol_name);
+ const typed_value = try self.resolveInstConst(block, export_inst.positionals.value);
switch (typed_value.ty.zigTypeTag()) {
.Fn => {},
@@ -280,18 +382,18 @@ const Analyze = struct {
/// TODO should not need the cast on the last parameter at the callsites
fn addNewInstArgs(
self: *Analyze,
- func: *Fn,
+ block: *Block,
src: usize,
ty: Type,
comptime T: type,
args: Inst.Args(T),
) !*Inst {
- const inst = try self.addNewInst(func, src, ty, T);
+ const inst = try self.addNewInst(block, src, ty, T);
inst.args = args;
return &inst.base;
}
- fn addNewInst(self: *Analyze, func: *Fn, src: usize, ty: Type, comptime T: type) !*T {
+ fn addNewInst(self: *Analyze, block: *Block, src: usize, ty: Type, comptime T: type) !*T {
const inst = try self.arena.allocator.create(T);
inst.* = .{
.base = .{
@@ -301,7 +403,7 @@ const Analyze = struct {
},
.args = undefined,
};
- try func.body.append(&inst.base);
+ try block.instructions.append(&inst.base);
return inst;
}
@@ -344,7 +446,21 @@ const Analyze = struct {
fn constVoid(self: *Analyze, src: usize) !*Inst {
return self.constInst(src, .{
.ty = Type.initTag(.void),
- .val = Value.initTag(.void_value),
+ .val = Value.initTag(.the_one_possible_value),
+ });
+ }
+
+ fn constUndef(self: *Analyze, src: usize, ty: Type) !*Inst {
+ return self.constInst(src, .{
+ .ty = ty,
+ .val = Value.initTag(.undef),
+ });
+ }
+
+ fn constBool(self: *Analyze, src: usize, v: bool) !*Inst {
+ return self.constInst(src, .{
+ .ty = Type.initTag(.bool),
+ .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)],
});
}
@@ -368,34 +484,38 @@ const Analyze = struct {
});
}
- fn constIntBig(self: *Analyze, src: usize, ty: Type, big_int: BigInt) !*Inst {
- if (big_int.isPositive()) {
+ fn constIntBig(self: *Analyze, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
+ const val_payload = if (big_int.positive) blk: {
if (big_int.to(u64)) |x| {
return self.constIntUnsigned(src, ty, x);
} else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below
}
- } else {
+ const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBigPositive);
+ big_int_payload.* = .{ .limbs = big_int.limbs };
+ break :blk &big_int_payload.base;
+ } else blk: {
if (big_int.to(i64)) |x| {
return self.constIntSigned(src, ty, x);
} else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below
}
- }
-
- const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBig);
- big_int_payload.* = .{ .big_int = big_int };
+ const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBigNegative);
+ big_int_payload.* = .{ .limbs = big_int.limbs };
+ break :blk &big_int_payload.base;
+ };
return self.constInst(src, .{
.ty = ty,
- .val = Value.initPayload(&big_int_payload.base),
+ .val = Value.initPayload(val_payload),
});
}
- fn analyzeInst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst {
+ fn analyzeInst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!*Inst {
switch (old_inst.tag) {
+ .breakpoint => return self.analyzeInstBreakpoint(block, old_inst.cast(text.Inst.Breakpoint).?),
.str => {
// We can use this reference because Inst.Const's Value is arena-allocated.
// The value would get copied to a MemoryCell before the `text.Inst.Str` lifetime ends.
@@ -406,35 +526,49 @@ const Analyze = struct {
const big_int = old_inst.cast(text.Inst.Int).?.positionals.int;
return self.constIntBig(old_inst.src, Type.initTag(.comptime_int), big_int);
},
- .ptrtoint => return self.analyzeInstPtrToInt(func, old_inst.cast(text.Inst.PtrToInt).?),
- .fieldptr => return self.analyzeInstFieldPtr(func, old_inst.cast(text.Inst.FieldPtr).?),
- .deref => return self.analyzeInstDeref(func, old_inst.cast(text.Inst.Deref).?),
- .as => return self.analyzeInstAs(func, old_inst.cast(text.Inst.As).?),
- .@"asm" => return self.analyzeInstAsm(func, old_inst.cast(text.Inst.Asm).?),
- .@"unreachable" => return self.analyzeInstUnreachable(func, old_inst.cast(text.Inst.Unreachable).?),
- .@"fn" => return self.analyzeInstFn(func, old_inst.cast(text.Inst.Fn).?),
+ .ptrtoint => return self.analyzeInstPtrToInt(block, old_inst.cast(text.Inst.PtrToInt).?),
+ .fieldptr => return self.analyzeInstFieldPtr(block, old_inst.cast(text.Inst.FieldPtr).?),
+ .deref => return self.analyzeInstDeref(block, old_inst.cast(text.Inst.Deref).?),
+ .as => return self.analyzeInstAs(block, old_inst.cast(text.Inst.As).?),
+ .@"asm" => return self.analyzeInstAsm(block, old_inst.cast(text.Inst.Asm).?),
+ .@"unreachable" => return self.analyzeInstUnreachable(block, old_inst.cast(text.Inst.Unreachable).?),
+ .@"return" => return self.analyzeInstRet(block, old_inst.cast(text.Inst.Return).?),
+ .@"fn" => return self.analyzeInstFn(block, old_inst.cast(text.Inst.Fn).?),
.@"export" => {
- try self.analyzeExport(func, old_inst.cast(text.Inst.Export).?);
+ try self.analyzeExport(block, old_inst.cast(text.Inst.Export).?);
return self.constVoid(old_inst.src);
},
- .primitive => return self.analyzeInstPrimitive(func, old_inst.cast(text.Inst.Primitive).?),
- .fntype => return self.analyzeInstFnType(func, old_inst.cast(text.Inst.FnType).?),
- .intcast => return self.analyzeInstIntCast(func, old_inst.cast(text.Inst.IntCast).?),
- .bitcast => return self.analyzeInstBitCast(func, old_inst.cast(text.Inst.BitCast).?),
- .elemptr => return self.analyzeInstElemPtr(func, old_inst.cast(text.Inst.ElemPtr).?),
- .add => return self.analyzeInstAdd(func, old_inst.cast(text.Inst.Add).?),
+ .primitive => return self.analyzeInstPrimitive(old_inst.cast(text.Inst.Primitive).?),
+ .fntype => return self.analyzeInstFnType(block, old_inst.cast(text.Inst.FnType).?),
+ .intcast => return self.analyzeInstIntCast(block, old_inst.cast(text.Inst.IntCast).?),
+ .bitcast => return self.analyzeInstBitCast(block, old_inst.cast(text.Inst.BitCast).?),
+ .elemptr => return self.analyzeInstElemPtr(block, old_inst.cast(text.Inst.ElemPtr).?),
+ .add => return self.analyzeInstAdd(block, old_inst.cast(text.Inst.Add).?),
+ .cmp => return self.analyzeInstCmp(block, old_inst.cast(text.Inst.Cmp).?),
+ .condbr => return self.analyzeInstCondBr(block, old_inst.cast(text.Inst.CondBr).?),
+ .isnull => return self.analyzeInstIsNull(block, old_inst.cast(text.Inst.IsNull).?),
+ .isnonnull => return self.analyzeInstIsNonNull(block, old_inst.cast(text.Inst.IsNonNull).?),
}
}
- fn analyzeInstFn(self: *Analyze, opt_func: ?*Fn, fn_inst: *text.Inst.Fn) InnerError!*Inst {
- const fn_type = try self.resolveType(opt_func, fn_inst.positionals.fn_type);
+ fn analyzeInstBreakpoint(self: *Analyze, block: ?*Block, inst: *text.Inst.Breakpoint) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(block, inst.base.src);
+ return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){});
+ }
+
+ fn analyzeInstFn(self: *Analyze, block: ?*Block, fn_inst: *text.Inst.Fn) InnerError!*Inst {
+ const fn_type = try self.resolveType(block, fn_inst.positionals.fn_type);
var new_func: Fn = .{
- .body = std.ArrayList(*Inst).init(self.allocator),
- .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator),
.fn_index = self.fns.items.len,
+ .inner_block = .{
+ .func = undefined,
+ .instructions = std.ArrayList(*Inst).init(self.allocator),
+ },
+ .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator),
};
- defer new_func.body.deinit();
+ new_func.inner_block.func = &new_func;
+ defer new_func.inner_block.instructions.deinit();
defer new_func.inst_table.deinit();
// Don't hang on to a reference to this when analyzing body instructions, since the memory
// could become invalid.
@@ -444,18 +578,11 @@ const Analyze = struct {
.body = undefined,
};
- for (fn_inst.positionals.body.instructions) |src_inst| {
- const new_inst = self.analyzeInst(&new_func, src_inst) catch |err| {
- self.fns.items[new_func.fn_index].analysis_status = .failure;
- try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = null });
- return err;
- };
- try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
- }
+ try self.analyzeBody(&new_func.inner_block, fn_inst.positionals.body);
const f = &self.fns.items[new_func.fn_index];
f.analysis_status = .success;
- f.body = new_func.body.toOwnedSlice();
+ f.body = .{ .instructions = new_func.inner_block.instructions.toOwnedSlice() };
const fn_payload = try self.arena.allocator.create(Value.Payload.Function);
fn_payload.* = .{ .index = new_func.fn_index };
@@ -466,8 +593,8 @@ const Analyze = struct {
});
}
- fn analyzeInstFnType(self: *Analyze, func: ?*Fn, fntype: *text.Inst.FnType) InnerError!*Inst {
- const return_type = try self.resolveType(func, fntype.positionals.return_type);
+ fn analyzeInstFnType(self: *Analyze, block: ?*Block, fntype: *text.Inst.FnType) InnerError!*Inst {
+ const return_type = try self.resolveType(block, fntype.positionals.return_type);
if (return_type.zigTypeTag() == .NoReturn and
fntype.positionals.param_types.len == 0 and
@@ -476,33 +603,40 @@ const Analyze = struct {
return self.constType(fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args));
}
+ if (return_type.zigTypeTag() == .Void and
+ fntype.positionals.param_types.len == 0 and
+ fntype.kw_args.cc == .C)
+ {
+ return self.constType(fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
+ }
+
return self.fail(fntype.base.src, "TODO implement fntype instruction more", .{});
}
- fn analyzeInstPrimitive(self: *Analyze, func: ?*Fn, primitive: *text.Inst.Primitive) InnerError!*Inst {
+ fn analyzeInstPrimitive(self: *Analyze, primitive: *text.Inst.Primitive) InnerError!*Inst {
return self.constType(primitive.base.src, primitive.positionals.tag.toType());
}
- fn analyzeInstAs(self: *Analyze, func: ?*Fn, as: *text.Inst.As) InnerError!*Inst {
- const dest_type = try self.resolveType(func, as.positionals.dest_type);
- const new_inst = try self.resolveInst(func, as.positionals.value);
- return self.coerce(func, dest_type, new_inst);
+ fn analyzeInstAs(self: *Analyze, block: ?*Block, as: *text.Inst.As) InnerError!*Inst {
+ const dest_type = try self.resolveType(block, as.positionals.dest_type);
+ const new_inst = try self.resolveInst(block, as.positionals.value);
+ return self.coerce(block, dest_type, new_inst);
}
- fn analyzeInstPtrToInt(self: *Analyze, func: ?*Fn, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst {
- const ptr = try self.resolveInst(func, ptrtoint.positionals.ptr);
+ fn analyzeInstPtrToInt(self: *Analyze, block: ?*Block, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst {
+ const ptr = try self.resolveInst(block, ptrtoint.positionals.ptr);
if (ptr.ty.zigTypeTag() != .Pointer) {
return self.fail(ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty});
}
// TODO handle known-pointer-address
- const f = try self.requireFunctionBody(func, ptrtoint.base.src);
+ const b = try self.requireRuntimeBlock(block, ptrtoint.base.src);
const ty = Type.initTag(.usize);
- return self.addNewInstArgs(f, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr });
+ return self.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr });
}
- fn analyzeInstFieldPtr(self: *Analyze, func: ?*Fn, fieldptr: *text.Inst.FieldPtr) InnerError!*Inst {
- const object_ptr = try self.resolveInst(func, fieldptr.positionals.object_ptr);
- const field_name = try self.resolveConstString(func, fieldptr.positionals.field_name);
+ fn analyzeInstFieldPtr(self: *Analyze, block: ?*Block, fieldptr: *text.Inst.FieldPtr) InnerError!*Inst {
+ const object_ptr = try self.resolveInst(block, fieldptr.positionals.object_ptr);
+ const field_name = try self.resolveConstString(block, fieldptr.positionals.field_name);
const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
.Pointer => object_ptr.ty.elemType(),
@@ -533,9 +667,9 @@ const Analyze = struct {
}
}
- fn analyzeInstIntCast(self: *Analyze, func: ?*Fn, intcast: *text.Inst.IntCast) InnerError!*Inst {
- const dest_type = try self.resolveType(func, intcast.positionals.dest_type);
- const new_inst = try self.resolveInst(func, intcast.positionals.value);
+ fn analyzeInstIntCast(self: *Analyze, block: ?*Block, intcast: *text.Inst.IntCast) InnerError!*Inst {
+ const dest_type = try self.resolveType(block, intcast.positionals.dest_type);
+ const new_inst = try self.resolveInst(block, intcast.positionals.value);
const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
.ComptimeInt => true,
@@ -559,22 +693,22 @@ const Analyze = struct {
}
if (dest_is_comptime_int or new_inst.value() != null) {
- return self.coerce(func, dest_type, new_inst);
+ return self.coerce(block, dest_type, new_inst);
}
return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{});
}
- fn analyzeInstBitCast(self: *Analyze, func: ?*Fn, inst: *text.Inst.BitCast) InnerError!*Inst {
- const dest_type = try self.resolveType(func, inst.positionals.dest_type);
- const operand = try self.resolveInst(func, inst.positionals.operand);
- return self.bitcast(func, dest_type, operand);
+ fn analyzeInstBitCast(self: *Analyze, block: ?*Block, inst: *text.Inst.BitCast) InnerError!*Inst {
+ const dest_type = try self.resolveType(block, inst.positionals.dest_type);
+ const operand = try self.resolveInst(block, inst.positionals.operand);
+ return self.bitcast(block, dest_type, operand);
}
- fn analyzeInstElemPtr(self: *Analyze, func: ?*Fn, inst: *text.Inst.ElemPtr) InnerError!*Inst {
- const array_ptr = try self.resolveInst(func, inst.positionals.array_ptr);
- const uncasted_index = try self.resolveInst(func, inst.positionals.index);
- const elem_index = try self.coerce(func, Type.initTag(.usize), uncasted_index);
+ fn analyzeInstElemPtr(self: *Analyze, block: ?*Block, inst: *text.Inst.ElemPtr) InnerError!*Inst {
+ const array_ptr = try self.resolveInst(block, inst.positionals.array_ptr);
+ const uncasted_index = try self.resolveInst(block, inst.positionals.index);
+ const elem_index = try self.coerce(block, Type.initTag(.usize), uncasted_index);
if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) {
if (array_ptr.value()) |array_ptr_val| {
@@ -602,28 +736,44 @@ const Analyze = struct {
return self.fail(inst.base.src, "TODO implement more analyze elemptr", .{});
}
- fn analyzeInstAdd(self: *Analyze, func: ?*Fn, inst: *text.Inst.Add) InnerError!*Inst {
- const lhs = try self.resolveInst(func, inst.positionals.lhs);
- const rhs = try self.resolveInst(func, inst.positionals.rhs);
+ fn analyzeInstAdd(self: *Analyze, block: ?*Block, inst: *text.Inst.Add) InnerError!*Inst {
+ const lhs = try self.resolveInst(block, inst.positionals.lhs);
+ const rhs = try self.resolveInst(block, inst.positionals.rhs);
if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) {
if (lhs.value()) |lhs_val| {
if (rhs.value()) |rhs_val| {
- const lhs_bigint = try lhs_val.toBigInt(&self.arena.allocator);
- const rhs_bigint = try rhs_val.toBigInt(&self.arena.allocator);
- var result_bigint = try BigInt.init(&self.arena.allocator);
- try BigInt.add(&result_bigint, lhs_bigint, rhs_bigint);
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs_val.toBigInt(&lhs_space);
+ const rhs_bigint = rhs_val.toBigInt(&rhs_space);
+ const limbs = try self.arena.allocator.alloc(
+ std.math.big.Limb,
+ std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
+ );
+ var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+ result_bigint.add(lhs_bigint, rhs_bigint);
+ const result_limbs = result_bigint.limbs[0..result_bigint.len];
if (!lhs.ty.eql(rhs.ty)) {
return self.fail(inst.base.src, "TODO implement peer type resolution", .{});
}
- const val_payload = try self.arena.allocator.create(Value.Payload.IntBig);
- val_payload.* = .{ .big_int = result_bigint };
+ const val_payload = if (result_bigint.positive) blk: {
+ const val_payload = try self.arena.allocator.create(Value.Payload.IntBigPositive);
+ val_payload.* = .{ .limbs = result_limbs };
+ break :blk &val_payload.base;
+ } else blk: {
+ const val_payload = try self.arena.allocator.create(Value.Payload.IntBigNegative);
+ val_payload.* = .{ .limbs = result_limbs };
+ break :blk &val_payload.base;
+ };
return self.constInst(inst.base.src, .{
.ty = lhs.ty,
- .val = Value.initPayload(&val_payload.base),
+ .val = Value.initPayload(val_payload),
});
}
}
@@ -632,8 +782,8 @@ const Analyze = struct {
return self.fail(inst.base.src, "TODO implement more analyze add", .{});
}
- fn analyzeInstDeref(self: *Analyze, func: ?*Fn, deref: *text.Inst.Deref) InnerError!*Inst {
- const ptr = try self.resolveInst(func, deref.positionals.ptr);
+ fn analyzeInstDeref(self: *Analyze, block: ?*Block, deref: *text.Inst.Deref) InnerError!*Inst {
+ const ptr = try self.resolveInst(block, deref.positionals.ptr);
const elem_ty = switch (ptr.ty.zigTypeTag()) {
.Pointer => ptr.ty.elemType(),
else => return self.fail(deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}),
@@ -648,28 +798,28 @@ const Analyze = struct {
return self.fail(deref.base.src, "TODO implement runtime deref", .{});
}
- fn analyzeInstAsm(self: *Analyze, func: ?*Fn, assembly: *text.Inst.Asm) InnerError!*Inst {
- const return_type = try self.resolveType(func, assembly.positionals.return_type);
- const asm_source = try self.resolveConstString(func, assembly.positionals.asm_source);
- const output = if (assembly.kw_args.output) |o| try self.resolveConstString(func, o) else null;
+ fn analyzeInstAsm(self: *Analyze, block: ?*Block, assembly: *text.Inst.Asm) InnerError!*Inst {
+ const return_type = try self.resolveType(block, assembly.positionals.return_type);
+ const asm_source = try self.resolveConstString(block, assembly.positionals.asm_source);
+ const output = if (assembly.kw_args.output) |o| try self.resolveConstString(block, o) else null;
const inputs = try self.arena.allocator.alloc([]const u8, assembly.kw_args.inputs.len);
const clobbers = try self.arena.allocator.alloc([]const u8, assembly.kw_args.clobbers.len);
const args = try self.arena.allocator.alloc(*Inst, assembly.kw_args.args.len);
for (inputs) |*elem, i| {
- elem.* = try self.resolveConstString(func, assembly.kw_args.inputs[i]);
+ elem.* = try self.resolveConstString(block, assembly.kw_args.inputs[i]);
}
for (clobbers) |*elem, i| {
- elem.* = try self.resolveConstString(func, assembly.kw_args.clobbers[i]);
+ elem.* = try self.resolveConstString(block, assembly.kw_args.clobbers[i]);
}
for (args) |*elem, i| {
- const arg = try self.resolveInst(func, assembly.kw_args.args[i]);
- elem.* = try self.coerce(func, Type.initTag(.usize), arg);
+ const arg = try self.resolveInst(block, assembly.kw_args.args[i]);
+ elem.* = try self.coerce(block, Type.initTag(.usize), arg);
}
- const f = try self.requireFunctionBody(func, assembly.base.src);
- return self.addNewInstArgs(f, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){
+ const b = try self.requireRuntimeBlock(block, assembly.base.src);
+ return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){
.asm_source = asm_source,
.is_volatile = assembly.kw_args.@"volatile",
.output = output,
@@ -679,19 +829,370 @@ const Analyze = struct {
});
}
- fn analyzeInstUnreachable(self: *Analyze, func: ?*Fn, unreach: *text.Inst.Unreachable) InnerError!*Inst {
- const f = try self.requireFunctionBody(func, unreach.base.src);
- return self.addNewInstArgs(f, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
+ fn analyzeInstCmp(self: *Analyze, block: ?*Block, inst: *text.Inst.Cmp) InnerError!*Inst {
+ const lhs = try self.resolveInst(block, inst.positionals.lhs);
+ const rhs = try self.resolveInst(block, inst.positionals.rhs);
+ const op = inst.positionals.op;
+
+ const is_equality_cmp = switch (op) {
+ .eq, .neq => true,
+ else => false,
+ };
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+ if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
+ // null == null, null != null
+ return self.constBool(inst.base.src, op == .eq);
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
+ rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
+ {
+ // comparing null with optionals
+ const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
+ if (opt_operand.value()) |opt_val| {
+ const is_null = opt_val.isNull();
+ return self.constBool(inst.base.src, if (op == .eq) is_null else !is_null);
+ }
+ const b = try self.requireRuntimeBlock(block, inst.base.src);
+ switch (op) {
+ .eq => return self.addNewInstArgs(
+ b,
+ inst.base.src,
+ Type.initTag(.bool),
+ Inst.IsNull,
+ Inst.Args(Inst.IsNull){ .operand = opt_operand },
+ ),
+ .neq => return self.addNewInstArgs(
+ b,
+ inst.base.src,
+ Type.initTag(.bool),
+ Inst.IsNonNull,
+ Inst.Args(Inst.IsNonNull){ .operand = opt_operand },
+ ),
+ else => unreachable,
+ }
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr())))
+ {
+ return self.fail(inst.base.src, "TODO implement C pointer cmp", .{});
+ } else if (lhs_ty_tag == .Null or rhs_ty_tag == .Null) {
+ const non_null_type = if (lhs_ty_tag == .Null) rhs.ty else lhs.ty;
+ return self.fail(inst.base.src, "comparison of '{}' with null", .{non_null_type});
+ } else if (is_equality_cmp and
+ ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or
+ (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union)))
+ {
+ return self.fail(inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) {
+ if (!is_equality_cmp) {
+ return self.fail(inst.base.src, "{} operator not allowed for errors", .{@tagName(op)});
+ }
+ return self.fail(inst.base.src, "TODO implement equality comparison between errors", .{});
+ } else if (lhs.ty.isNumeric() and rhs.ty.isNumeric()) {
+ // This operation allows any combination of integer and float types, regardless of the
+ // signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
+ // numeric types.
+ return self.cmpNumeric(block, inst.base.src, lhs, rhs, op);
+ }
+ return self.fail(inst.base.src, "TODO implement more cmp analysis", .{});
+ }
+
+ fn analyzeInstIsNull(self: *Analyze, block: ?*Block, inst: *text.Inst.IsNull) InnerError!*Inst {
+ const operand = try self.resolveInst(block, inst.positionals.operand);
+ return self.analyzeIsNull(block, inst.base.src, operand, true);
+ }
+
+ fn analyzeInstIsNonNull(self: *Analyze, block: ?*Block, inst: *text.Inst.IsNonNull) InnerError!*Inst {
+ const operand = try self.resolveInst(block, inst.positionals.operand);
+ return self.analyzeIsNull(block, inst.base.src, operand, false);
+ }
+
+ fn analyzeInstCondBr(self: *Analyze, block: ?*Block, inst: *text.Inst.CondBr) InnerError!*Inst {
+ const uncasted_cond = try self.resolveInst(block, inst.positionals.condition);
+ const cond = try self.coerce(block, Type.initTag(.bool), uncasted_cond);
+
+ if (try self.resolveDefinedValue(cond)) |cond_val| {
+ const body = if (cond_val.toBool()) &inst.positionals.true_body else &inst.positionals.false_body;
+ try self.analyzeBody(block, body.*);
+ return self.constVoid(inst.base.src);
+ }
+
+ const parent_block = try self.requireRuntimeBlock(block, inst.base.src);
+
+ var true_block: Block = .{
+ .func = parent_block.func,
+ .instructions = std.ArrayList(*Inst).init(self.allocator),
+ };
+ defer true_block.instructions.deinit();
+ try self.analyzeBody(&true_block, inst.positionals.true_body);
+
+ var false_block: Block = .{
+ .func = parent_block.func,
+ .instructions = std.ArrayList(*Inst).init(self.allocator),
+ };
+ defer false_block.instructions.deinit();
+ try self.analyzeBody(&false_block, inst.positionals.false_body);
+
+ // Copy the instruction pointers to the arena memory
+ const true_instructions = try self.arena.allocator.alloc(*Inst, true_block.instructions.items.len);
+ const false_instructions = try self.arena.allocator.alloc(*Inst, false_block.instructions.items.len);
+
+ mem.copy(*Inst, true_instructions, true_block.instructions.items);
+ mem.copy(*Inst, false_instructions, false_block.instructions.items);
+
+ return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){
+ .condition = cond,
+ .true_body = .{ .instructions = true_instructions },
+ .false_body = .{ .instructions = false_instructions },
+ });
+ }
+
+ fn wantSafety(self: *Analyze, block: ?*Block) bool {
+ return switch (self.optimize_mode) {
+ .Debug => true,
+ .ReleaseSafe => true,
+ .ReleaseFast => false,
+ .ReleaseSmall => false,
+ };
+ }
+
+ fn analyzeInstUnreachable(self: *Analyze, block: ?*Block, unreach: *text.Inst.Unreachable) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(block, unreach.base.src);
+ if (self.wantSafety(block)) {
+ // TODO Once we have a panic function to call, call it here instead of this.
+ _ = try self.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {});
+ }
+ return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
+ }
+
+ fn analyzeInstRet(self: *Analyze, block: ?*Block, inst: *text.Inst.Return) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(block, inst.base.src);
+ return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {});
+ }
+
+ fn analyzeBody(self: *Analyze, block: ?*Block, body: text.Module.Body) !void {
+ for (body.instructions) |src_inst| {
+ const new_inst = self.analyzeInst(block, src_inst) catch |err| {
+ if (block) |b| {
+ self.fns.items[b.func.fn_index].analysis_status = .failure;
+ try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = null });
+ }
+ return err;
+ };
+ if (block) |b| try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
+ }
+ }
+
+ fn analyzeIsNull(
+ self: *Analyze,
+ block: ?*Block,
+ src: usize,
+ operand: *Inst,
+ invert_logic: bool,
+ ) InnerError!*Inst {
+ return self.fail(src, "TODO implement analysis of isnull and isnotnull", .{});
+ }
+
+ /// Asserts that lhs and rhs types are both numeric.
+ fn cmpNumeric(
+ self: *Analyze,
+ block: ?*Block,
+ src: usize,
+ lhs: *Inst,
+ rhs: *Inst,
+ op: std.math.CompareOperator,
+ ) !*Inst {
+ assert(lhs.ty.isNumeric());
+ assert(rhs.ty.isNumeric());
+
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+
+ if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return self.fail(src, "vector length mismatch: {} and {}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return self.fail(src, "TODO implement support for vectors in cmpNumeric", .{});
+ } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) {
+ return self.fail(src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ if (lhs.value()) |lhs_val| {
+ if (rhs.value()) |rhs_val| {
+ return self.constBool(src, Value.compare(lhs_val, op, rhs_val));
+ }
+ }
+
+ // TODO handle comparisons against lazy zero values
+ // Some values can be compared against zero without being runtime known or without forcing
+ // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
+ // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
+ // of this function if we don't need to.
+
+ // It must be a runtime comparison.
+ const b = try self.requireRuntimeBlock(block, src);
+ // For floats, emit a float comparison instruction.
+ const lhs_is_float = switch (lhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ const rhs_is_float = switch (rhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ if (lhs_is_float and rhs_is_float) {
+ // Implicit cast the smaller one to the larger one.
+ const dest_type = x: {
+ if (lhs_ty_tag == .ComptimeFloat) {
+ break :x rhs.ty;
+ } else if (rhs_ty_tag == .ComptimeFloat) {
+ break :x lhs.ty;
+ }
+ if (lhs.ty.floatBits(self.target) >= rhs.ty.floatBits(self.target)) {
+ break :x lhs.ty;
+ } else {
+ break :x rhs.ty;
+ }
+ };
+ const casted_lhs = try self.coerce(block, dest_type, lhs);
+ const casted_rhs = try self.coerce(block, dest_type, rhs);
+ return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
+ .lhs = casted_lhs,
+ .rhs = casted_rhs,
+ .op = op,
+ });
+ }
+ // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
+ // For mixed signed and unsigned integers, implicit cast both operands to a signed
+ // integer with + 1 bit.
+ // For mixed floats and integers, extract the integer part from the float, cast that to
+ // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
+ // add/subtract 1.
+ const lhs_is_signed = if (lhs.value()) |lhs_val|
+ lhs_val.compareWithZero(.lt)
+ else
+ (lhs.ty.isFloat() or lhs.ty.isSignedInt());
+ const rhs_is_signed = if (rhs.value()) |rhs_val|
+ rhs_val.compareWithZero(.lt)
+ else
+ (rhs.ty.isFloat() or rhs.ty.isSignedInt());
+ const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
+
+ var dest_float_type: ?Type = null;
+
+ var lhs_bits: usize = undefined;
+ if (lhs.value()) |lhs_val| {
+ if (lhs_val.isUndef())
+ return self.constUndef(src, Type.initTag(.bool));
+ const is_unsigned = if (lhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
+ defer bigint.deinit();
+ const zcmp = lhs_val.orderAgainstZero();
+ if (lhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return self.constBool(src, false),
+ .neq => return self.constBool(src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ lhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ lhs_bits = lhs_val.intBitCountTwosComp();
+ break :x (lhs_val.orderAgainstZero() != .lt);
+ };
+ lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (lhs_is_float) {
+ dest_float_type = lhs.ty;
+ } else {
+ const int_info = lhs.ty.intInfo(self.target);
+ lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
+ }
+
+ var rhs_bits: usize = undefined;
+ if (rhs.value()) |rhs_val| {
+ if (rhs_val.isUndef())
+ return self.constUndef(src, Type.initTag(.bool));
+ const is_unsigned = if (rhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
+ defer bigint.deinit();
+ const zcmp = rhs_val.orderAgainstZero();
+ if (rhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return self.constBool(src, false),
+ .neq => return self.constBool(src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ rhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ rhs_bits = rhs_val.intBitCountTwosComp();
+ break :x (rhs_val.orderAgainstZero() != .lt);
+ };
+ rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (rhs_is_float) {
+ dest_float_type = rhs.ty;
+ } else {
+ const int_info = rhs.ty.intInfo(self.target);
+ rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
+ }
+
+ const dest_type = if (dest_float_type) |ft| ft else blk: {
+ const max_bits = std.math.max(lhs_bits, rhs_bits);
+ const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) {
+ error.Overflow => return self.fail(src, "{} exceeds maximum integer bit count", .{max_bits}),
+ };
+ break :blk try self.makeIntType(dest_int_is_signed, casted_bits);
+ };
+ const casted_lhs = try self.coerce(block, dest_type, lhs);
+ const casted_rhs = try self.coerce(block, dest_type, lhs);
+
+ return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
+ .lhs = casted_lhs,
+ .rhs = casted_rhs,
+ .op = op,
+ });
+ }
+
+ fn makeIntType(self: *Analyze, signed: bool, bits: u16) !Type {
+ if (signed) {
+ const int_payload = try self.arena.allocator.create(Type.Payload.IntSigned);
+ int_payload.* = .{ .bits = bits };
+ return Type.initPayload(&int_payload.base);
+ } else {
+ const int_payload = try self.arena.allocator.create(Type.Payload.IntUnsigned);
+ int_payload.* = .{ .bits = bits };
+ return Type.initPayload(&int_payload.base);
+ }
}
- fn coerce(self: *Analyze, func: ?*Fn, dest_type: Type, inst: *Inst) !*Inst {
+ fn coerce(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst {
// If the types are the same, we can return the operand.
if (dest_type.eql(inst.ty))
return inst;
const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
if (in_memory_result == .ok) {
- return self.bitcast(func, dest_type, inst);
+ return self.bitcast(block, dest_type, inst);
}
// *[N]T to []T
@@ -735,14 +1236,14 @@ const Analyze = struct {
return self.fail(inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type });
}
- fn bitcast(self: *Analyze, func: ?*Fn, dest_type: Type, inst: *Inst) !*Inst {
+ fn bitcast(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst {
if (inst.value()) |val| {
// Keep the comptime Value representation; take the new type.
return self.constInst(inst.src, .{ .ty = dest_type, .val = val });
}
// TODO validate the type size and other compile errors
- const f = try self.requireFunctionBody(func, inst.src);
- return self.addNewInstArgs(f, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst });
+ const b = try self.requireRuntimeBlock(block, inst.src);
+ return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst });
}
fn coerceArrayPtrToSlice(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
@@ -784,18 +1285,20 @@ pub fn main() anyerror!void {
const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator;
const args = try std.process.argsAlloc(allocator);
+ defer std.process.argsFree(allocator, args);
const src_path = args[1];
const debug_error_trace = true;
const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0);
+ defer allocator.free(source);
var zir_module = try text.parse(allocator, source);
defer zir_module.deinit(allocator);
if (zir_module.errors.len != 0) {
for (zir_module.errors) |err_msg| {
- const loc = findLineColumn(source, err_msg.byte_offset);
+ const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
}
if (debug_error_trace) return error.ParseFailure;
@@ -804,15 +1307,20 @@ pub fn main() anyerror!void {
const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
- var analyzed_module = try analyze(allocator, zir_module, native_info.target);
+ var analyzed_module = try analyze(allocator, zir_module, .{
+ .target = native_info.target,
+ .output_mode = .Obj,
+ .link_mode = .Static,
+ .optimize_mode = .Debug,
+ });
defer analyzed_module.deinit(allocator);
if (analyzed_module.errors.len != 0) {
for (analyzed_module.errors) |err_msg| {
- const loc = findLineColumn(source, err_msg.byte_offset);
+ const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
}
- if (debug_error_trace) return error.ParseFailure;
+ if (debug_error_trace) return error.AnalysisFail;
std.process.exit(1);
}
@@ -827,34 +1335,17 @@ pub fn main() anyerror!void {
}
const link = @import("link.zig");
- var result = try link.updateExecutableFilePath(allocator, analyzed_module, std.fs.cwd(), "a.out");
+ var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o");
defer result.deinit(allocator);
if (result.errors.len != 0) {
for (result.errors) |err_msg| {
- const loc = findLineColumn(source, err_msg.byte_offset);
+ const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
}
- if (debug_error_trace) return error.ParseFailure;
+ if (debug_error_trace) return error.LinkFailure;
std.process.exit(1);
}
}
-fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
- var line: usize = 0;
- var column: usize = 0;
- for (source[0..byte_offset]) |byte| {
- switch (byte) {
- '\n' => {
- line += 1;
- column = 0;
- },
- else => {
- column += 1;
- },
- }
- }
- return .{ .line = line, .column = column };
-}
-
// Performance optimization ideas:
// * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions
diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig
index 5d0b49b89e..e1efb40fe5 100644
--- a/src-self-hosted/ir/text.zig
+++ b/src-self-hosted/ir/text.zig
@@ -4,7 +4,8 @@ const std = @import("std");
const mem = std.mem;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
-const BigInt = std.math.big.Int;
+const BigIntConst = std.math.big.int.Const;
+const BigIntMutable = std.math.big.int.Mutable;
const Type = @import("../type.zig").Type;
const Value = @import("../value.zig").Value;
const ir = @import("../ir.zig");
@@ -18,6 +19,7 @@ pub const Inst = struct {
/// These names are used directly as the instruction names in the text format.
pub const Tag = enum {
+ breakpoint,
str,
int,
ptrtoint,
@@ -26,6 +28,7 @@ pub const Inst = struct {
as,
@"asm",
@"unreachable",
+ @"return",
@"fn",
@"export",
primitive,
@@ -34,10 +37,15 @@ pub const Inst = struct {
bitcast,
elemptr,
add,
+ cmp,
+ condbr,
+ isnull,
+ isnonnull,
};
pub fn TagToType(tag: Tag) type {
return switch (tag) {
+ .breakpoint => Breakpoint,
.str => Str,
.int => Int,
.ptrtoint => PtrToInt,
@@ -46,6 +54,7 @@ pub const Inst = struct {
.as => As,
.@"asm" => Asm,
.@"unreachable" => Unreachable,
+ .@"return" => Return,
.@"fn" => Fn,
.@"export" => Export,
.primitive => Primitive,
@@ -54,6 +63,10 @@ pub const Inst = struct {
.bitcast => BitCast,
.elemptr => ElemPtr,
.add => Add,
+ .cmp => Cmp,
+ .condbr => CondBr,
+ .isnull => IsNull,
+ .isnonnull => IsNonNull,
};
}
@@ -64,6 +77,14 @@ pub const Inst = struct {
return @fieldParentPtr(T, "base", base);
}
+ pub const Breakpoint = struct {
+ pub const base_tag = Tag.breakpoint;
+ base: Inst,
+
+ positionals: struct {},
+ kw_args: struct {},
+ };
+
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
@@ -79,7 +100,7 @@ pub const Inst = struct {
base: Inst,
positionals: struct {
- int: BigInt,
+ int: BigIntConst,
},
kw_args: struct {},
};
@@ -151,19 +172,23 @@ pub const Inst = struct {
kw_args: struct {},
};
+ pub const Return = struct {
+ pub const base_tag = Tag.@"return";
+ base: Inst,
+
+ positionals: struct {},
+ kw_args: struct {},
+ };
+
pub const Fn = struct {
pub const base_tag = Tag.@"fn";
base: Inst,
positionals: struct {
fn_type: *Inst,
- body: Body,
+ body: Module.Body,
},
kw_args: struct {},
-
- pub const Body = struct {
- instructions: []*Inst,
- };
};
pub const Export = struct {
@@ -297,6 +322,50 @@ pub const Inst = struct {
},
kw_args: struct {},
};
+
+ pub const Cmp = struct {
+ pub const base_tag = Tag.cmp;
+ base: Inst,
+
+ positionals: struct {
+ lhs: *Inst,
+ op: std.math.CompareOperator,
+ rhs: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const CondBr = struct {
+ pub const base_tag = Tag.condbr;
+ base: Inst,
+
+ positionals: struct {
+ condition: *Inst,
+ true_body: Module.Body,
+ false_body: Module.Body,
+ },
+ kw_args: struct {},
+ };
+
+ pub const IsNull = struct {
+ pub const base_tag = Tag.isnull;
+ base: Inst,
+
+ positionals: struct {
+ operand: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const IsNonNull = struct {
+ pub const base_tag = Tag.isnonnull;
+ base: Inst,
+
+ positionals: struct {
+ operand: *Inst,
+ },
+ kw_args: struct {},
+ };
};
pub const ErrorMsg = struct {
@@ -309,6 +378,10 @@ pub const Module = struct {
errors: []ErrorMsg,
arena: std.heap.ArenaAllocator,
+ pub const Body = struct {
+ instructions: []*Inst,
+ };
+
pub fn deinit(self: *Module, allocator: *Allocator) void {
allocator.free(self.decls);
allocator.free(self.errors);
@@ -321,7 +394,7 @@ pub const Module = struct {
self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {};
}
- const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Inst.Fn.Body });
+ const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Module.Body });
/// The allocator is used for temporary storage, but this function always returns
/// with no resources allocated.
@@ -357,6 +430,7 @@ pub const Module = struct {
) @TypeOf(stream).Error!void {
// TODO I tried implementing this with an inline for loop and hit a compiler bug
switch (decl.tag) {
+ .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table),
.str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
.int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
@@ -365,6 +439,7 @@ pub const Module = struct {
.as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table),
.@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table),
.@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table),
+ .@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table),
.@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table),
.@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table),
.primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table),
@@ -373,6 +448,10 @@ pub const Module = struct {
.bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, decl, inst_table),
.elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, decl, inst_table),
.add => return self.writeInstToStreamGeneric(stream, .add, decl, inst_table),
+ .cmp => return self.writeInstToStreamGeneric(stream, .cmp, decl, inst_table),
+ .condbr => return self.writeInstToStreamGeneric(stream, .condbr, decl, inst_table),
+ .isnull => return self.writeInstToStreamGeneric(stream, .isnull, decl, inst_table),
+ .isnonnull => return self.writeInstToStreamGeneric(stream, .isnonnull, decl, inst_table),
}
}
@@ -432,7 +511,7 @@ pub const Module = struct {
}
try stream.writeByte(']');
},
- Inst.Fn.Body => {
+ Module.Body => {
try stream.writeAll("{\n");
for (param.instructions) |inst, i| {
try stream.print(" %{} ", .{i});
@@ -443,7 +522,7 @@ pub const Module = struct {
},
bool => return stream.writeByte("01"[@boolToInt(param)]),
[]u8, []const u8 => return std.zig.renderStringLiteral(param, stream),
- BigInt => return stream.print("{}", .{param}),
+ BigIntConst => return stream.print("{}", .{param}),
else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
}
}
@@ -497,7 +576,7 @@ const Parser = struct {
name_map: std.StringHashMap(usize),
};
- fn parseBody(self: *Parser) !Inst.Fn.Body {
+ fn parseBody(self: *Parser) !Module.Body {
var body_context = Body{
.instructions = std.ArrayList(*Inst).init(self.allocator),
.name_map = std.StringHashMap(usize).init(self.allocator),
@@ -532,9 +611,10 @@ const Parser = struct {
else => |byte| return self.failByte(byte),
};
- return Inst.Fn.Body{
- .instructions = body_context.instructions.toOwnedSlice(),
- };
+ // Move the instructions to the arena
+ const instrs = try self.arena.allocator.alloc(*Inst, body_context.instructions.items.len);
+ mem.copy(*Inst, instrs, body_context.instructions.items);
+ return Module.Body{ .instructions = instrs };
}
fn parseStringLiteral(self: *Parser) ![]u8 {
@@ -565,7 +645,7 @@ const Parser = struct {
};
}
- fn parseIntegerLiteral(self: *Parser) !BigInt {
+ fn parseIntegerLiteral(self: *Parser) !BigIntConst {
const start = self.i;
if (self.source[self.i] == '-') self.i += 1;
while (true) : (self.i += 1) switch (self.source[self.i]) {
@@ -573,41 +653,46 @@ const Parser = struct {
else => break,
};
const number_text = self.source[start..self.i];
- var result = try BigInt.init(&self.arena.allocator);
- result.setString(10, number_text) catch |err| {
- self.i = start;
- switch (err) {
- error.InvalidBase => unreachable,
- error.InvalidCharForDigit => return self.fail("invalid digit in integer literal", .{}),
- error.DigitTooLargeForBase => return self.fail("digit too large in integer literal", .{}),
- else => |e| return e,
- }
+ const base = 10;
+ // TODO reuse the same array list for this
+ const limbs_buffer_len = std.math.big.int.calcSetStringLimbsBufferLen(base, number_text.len);
+ const limbs_buffer = try self.allocator.alloc(std.math.big.Limb, limbs_buffer_len);
+ defer self.allocator.free(limbs_buffer);
+ const limb_len = std.math.big.int.calcSetStringLimbCount(base, number_text.len);
+ const limbs = try self.arena.allocator.alloc(std.math.big.Limb, limb_len);
+ var result = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+ result.setString(base, number_text, limbs_buffer, self.allocator) catch |err| switch (err) {
+ error.InvalidCharacter => {
+ self.i = start;
+ return self.fail("invalid digit in integer literal", .{});
+ },
};
- return result;
+ return result.toConst();
}
fn parseRoot(self: *Parser) !void {
// The IR format is designed so that it can be tokenized and parsed at the same time.
- while (true) : (self.i += 1) switch (self.source[self.i]) {
- ';' => _ = try skipToAndOver(self, '\n'),
- '@' => {
- self.i += 1;
- const ident = try skipToAndOver(self, ' ');
- skipSpace(self);
- try requireEatBytes(self, "=");
- skipSpace(self);
- const inst = try parseInstruction(self, null);
- const ident_index = self.decls.items.len;
- if (try self.global_name_map.put(ident, ident_index)) |_| {
- return self.fail("redefinition of identifier '{}'", .{ident});
- }
- try self.decls.append(inst);
- continue;
- },
- ' ', '\n' => continue,
- 0 => break,
- else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
- };
+ while (true) {
+ switch (self.source[self.i]) {
+ ';' => _ = try skipToAndOver(self, '\n'),
+ '@' => {
+ self.i += 1;
+ const ident = try skipToAndOver(self, ' ');
+ skipSpace(self);
+ try requireEatBytes(self, "=");
+ skipSpace(self);
+ const inst = try parseInstruction(self, null);
+ const ident_index = self.decls.items.len;
+ if (try self.global_name_map.put(ident, ident_index)) |_| {
+ return self.fail("redefinition of identifier '{}'", .{ident});
+ }
+ try self.decls.append(inst);
+ },
+ ' ', '\n' => self.i += 1,
+ 0 => break,
+ else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
+ }
+ }
}
fn eatByte(self: *Parser, byte: u8) bool {
@@ -752,7 +837,7 @@ const Parser = struct {
};
}
switch (T) {
- Inst.Fn.Body => return parseBody(self),
+ Module.Body => return parseBody(self),
bool => {
const bool_value = switch (self.source[self.i]) {
'0' => false,
@@ -779,7 +864,7 @@ const Parser = struct {
},
*Inst => return parseParameterInst(self, body_ctx),
[]u8, []const u8 => return self.parseStringLiteral(),
- BigInt => return self.parseIntegerLiteral(),
+ BigIntConst => return self.parseIntegerLiteral(),
else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),
}
return self.fail("TODO parse parameter {}", .{@typeName(T)});
@@ -878,11 +963,12 @@ const EmitZIR = struct {
}
fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Inst {
+ const big_int_space = try self.arena.allocator.create(Value.BigIntSpace);
const int_inst = try self.arena.allocator.create(Inst.Int);
int_inst.* = .{
.base = .{ .src = src, .tag = Inst.Int.base_tag },
.positionals = .{
- .int = try val.toBigInt(&self.arena.allocator),
+ .int = val.toBigInt(big_int_space),
},
.kw_args = .{},
};
@@ -937,96 +1023,19 @@ const EmitZIR = struct {
var instructions = std.ArrayList(*Inst).init(self.allocator);
defer instructions.deinit();
- for (module_fn.body) |inst| {
- const new_inst = switch (inst.tag) {
- .unreach => blk: {
- const unreach_inst = try self.arena.allocator.create(Inst.Unreachable);
- unreach_inst.* = .{
- .base = .{ .src = inst.src, .tag = Inst.Unreachable.base_tag },
- .positionals = .{},
- .kw_args = .{},
- };
- break :blk &unreach_inst.base;
- },
- .constant => unreachable, // excluded from function bodies
- .assembly => blk: {
- const old_inst = inst.cast(ir.Inst.Assembly).?;
- const new_inst = try self.arena.allocator.create(Inst.Asm);
-
- const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len);
- for (inputs) |*elem, i| {
- elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]);
- }
-
- const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len);
- for (clobbers) |*elem, i| {
- elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]);
- }
-
- const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
- for (args) |*elem, i| {
- elem.* = try self.resolveInst(&inst_table, old_inst.args.args[i]);
- }
-
- new_inst.* = .{
- .base = .{ .src = inst.src, .tag = Inst.Asm.base_tag },
- .positionals = .{
- .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
- .return_type = try self.emitType(inst.src, inst.ty),
- },
- .kw_args = .{
- .@"volatile" = old_inst.args.is_volatile,
- .output = if (old_inst.args.output) |o|
- try self.emitStringLiteral(inst.src, o)
- else
- null,
- .inputs = inputs,
- .clobbers = clobbers,
- .args = args,
- },
- };
- break :blk &new_inst.base;
- },
- .ptrtoint => blk: {
- const old_inst = inst.cast(ir.Inst.PtrToInt).?;
- const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
- new_inst.* = .{
- .base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag },
- .positionals = .{
- .ptr = try self.resolveInst(&inst_table, old_inst.args.ptr),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- .bitcast => blk: {
- const old_inst = inst.cast(ir.Inst.BitCast).?;
- const new_inst = try self.arena.allocator.create(Inst.BitCast);
- new_inst.* = .{
- .base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag },
- .positionals = .{
- .dest_type = try self.emitType(inst.src, inst.ty),
- .operand = try self.resolveInst(&inst_table, old_inst.args.operand),
- },
- .kw_args = .{},
- };
- break :blk &new_inst.base;
- },
- };
- try instructions.append(new_inst);
- try inst_table.putNoClobber(inst, new_inst);
- }
+ try self.emitBody(module_fn.body, &inst_table, &instructions);
const fn_type = try self.emitType(src, module_fn.fn_type);
+ const arena_instrs = try self.arena.allocator.alloc(*Inst, instructions.items.len);
+ mem.copy(*Inst, arena_instrs, instructions.items);
+
const fn_inst = try self.arena.allocator.create(Inst.Fn);
fn_inst.* = .{
.base = .{ .src = src, .tag = Inst.Fn.base_tag },
.positionals = .{
.fn_type = fn_type,
- .body = .{
- .instructions = instructions.toOwnedSlice(),
- },
+ .body = .{ .instructions = arena_instrs },
},
.kw_args = .{},
};
@@ -1037,6 +1046,159 @@ const EmitZIR = struct {
}
}
+ fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
+ const new_inst = try self.arena.allocator.create(T);
+ new_inst.* = .{
+ .base = .{ .src = src, .tag = T.base_tag },
+ .positionals = .{},
+ .kw_args = .{},
+ };
+ return &new_inst.base;
+ }
+
+ fn emitBody(
+ self: *EmitZIR,
+ body: ir.Module.Body,
+ inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
+ instructions: *std.ArrayList(*Inst),
+ ) Allocator.Error!void {
+ for (body.instructions) |inst| {
+ const new_inst = switch (inst.tag) {
+ .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint),
+ .unreach => try self.emitTrivial(inst.src, Inst.Unreachable),
+ .ret => try self.emitTrivial(inst.src, Inst.Return),
+ .constant => unreachable, // excluded from function bodies
+ .assembly => blk: {
+ const old_inst = inst.cast(ir.Inst.Assembly).?;
+ const new_inst = try self.arena.allocator.create(Inst.Asm);
+
+ const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len);
+ for (inputs) |*elem, i| {
+ elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]);
+ }
+
+ const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len);
+ for (clobbers) |*elem, i| {
+ elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]);
+ }
+
+ const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
+ for (args) |*elem, i| {
+ elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]);
+ }
+
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.Asm.base_tag },
+ .positionals = .{
+ .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
+ .return_type = try self.emitType(inst.src, inst.ty),
+ },
+ .kw_args = .{
+ .@"volatile" = old_inst.args.is_volatile,
+ .output = if (old_inst.args.output) |o|
+ try self.emitStringLiteral(inst.src, o)
+ else
+ null,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ },
+ };
+ break :blk &new_inst.base;
+ },
+ .ptrtoint => blk: {
+ const old_inst = inst.cast(ir.Inst.PtrToInt).?;
+ const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag },
+ .positionals = .{
+ .ptr = try self.resolveInst(inst_table, old_inst.args.ptr),
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ .bitcast => blk: {
+ const old_inst = inst.cast(ir.Inst.BitCast).?;
+ const new_inst = try self.arena.allocator.create(Inst.BitCast);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag },
+ .positionals = .{
+ .dest_type = try self.emitType(inst.src, inst.ty),
+ .operand = try self.resolveInst(inst_table, old_inst.args.operand),
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ .cmp => blk: {
+ const old_inst = inst.cast(ir.Inst.Cmp).?;
+ const new_inst = try self.arena.allocator.create(Inst.Cmp);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.Cmp.base_tag },
+ .positionals = .{
+ .lhs = try self.resolveInst(inst_table, old_inst.args.lhs),
+ .rhs = try self.resolveInst(inst_table, old_inst.args.rhs),
+ .op = old_inst.args.op,
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ .condbr => blk: {
+ const old_inst = inst.cast(ir.Inst.CondBr).?;
+
+ var true_body = std.ArrayList(*Inst).init(self.allocator);
+ var false_body = std.ArrayList(*Inst).init(self.allocator);
+
+ defer true_body.deinit();
+ defer false_body.deinit();
+
+ try self.emitBody(old_inst.args.true_body, inst_table, &true_body);
+ try self.emitBody(old_inst.args.false_body, inst_table, &false_body);
+
+ const new_inst = try self.arena.allocator.create(Inst.CondBr);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.CondBr.base_tag },
+ .positionals = .{
+ .condition = try self.resolveInst(inst_table, old_inst.args.condition),
+ .true_body = .{ .instructions = true_body.toOwnedSlice() },
+ .false_body = .{ .instructions = false_body.toOwnedSlice() },
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ .isnull => blk: {
+ const old_inst = inst.cast(ir.Inst.IsNull).?;
+ const new_inst = try self.arena.allocator.create(Inst.IsNull);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.IsNull.base_tag },
+ .positionals = .{
+ .operand = try self.resolveInst(inst_table, old_inst.args.operand),
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ .isnonnull => blk: {
+ const old_inst = inst.cast(ir.Inst.IsNonNull).?;
+ const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.IsNonNull.base_tag },
+ .positionals = .{
+ .operand = try self.resolveInst(inst_table, old_inst.args.operand),
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ };
+ try instructions.append(new_inst);
+ try inst_table.putNoClobber(inst, new_inst);
+ }
+ }
+
fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Inst {
switch (ty.tag()) {
.isize => return self.emitPrimitiveType(src, .isize),
diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig
index cb6aa40afe..504c374ca7 100644
--- a/src-self-hosted/link.zig
+++ b/src-self-hosted/link.zig
@@ -7,11 +7,6 @@ const fs = std.fs;
const elf = std.elf;
const codegen = @import("codegen.zig");
-/// On common systems with a 0o022 umask, 0o777 will still result in a file created
-/// with 0o755 permissions, but it works appropriately if the system is configured
-/// more leniently. As another data point, C's fopen seems to open files with the
-/// 666 mode.
-const executable_mode = 0o777;
const default_entry_addr = 0x8000000;
pub const ErrorMsg = struct {
@@ -35,29 +30,29 @@ pub const Result = struct {
/// If incremental linking fails, falls back to truncating the file and rewriting it.
/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
/// This operation is not atomic.
-pub fn updateExecutableFilePath(
+pub fn updateFilePath(
allocator: *Allocator,
module: ir.Module,
dir: fs.Dir,
sub_path: []const u8,
) !Result {
- const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = executable_mode });
+ const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) });
defer file.close();
- return updateExecutableFile(allocator, module, file);
+ return updateFile(allocator, module, file);
}
/// Atomically overwrites the old file, if present.
-pub fn writeExecutableFilePath(
+pub fn writeFilePath(
allocator: *Allocator,
module: ir.Module,
dir: fs.Dir,
sub_path: []const u8,
) !Result {
- const af = try dir.atomicFile(sub_path, .{ .mode = executable_mode });
+ const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) });
defer af.deinit();
- const result = try writeExecutableFile(allocator, module, af.file);
+ const result = try writeFile(allocator, module, af.file);
try af.finish();
return result;
}
@@ -67,10 +62,10 @@ pub fn writeExecutableFilePath(
/// Returns an error if `file` is not already open with +read +write +seek abilities.
/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
/// This operation is not atomic.
-pub fn updateExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
- return updateExecutableFileInner(allocator, module, file) catch |err| switch (err) {
+pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+ return updateFileInner(allocator, module, file) catch |err| switch (err) {
error.IncrFailed => {
- return writeExecutableFile(allocator, module, file);
+ return writeFile(allocator, module, file);
},
else => |e| return e,
};
@@ -436,7 +431,7 @@ const Update = struct {
},
}
}
- if (self.entry_addr == null) {
+ if (self.entry_addr == null and self.module.output_mode == .Exe) {
const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{});
errdefer self.errors.allocator.free(msg);
try self.errors.append(.{
@@ -485,7 +480,15 @@ const Update = struct {
assert(index == 16);
- mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf.ET.EXEC), endian);
+ const elf_type = switch (self.module.output_mode) {
+ .Exe => elf.ET.EXEC,
+ .Obj => elf.ET.REL,
+ .Lib => switch (self.module.link_mode) {
+ .Static => elf.ET.REL,
+ .Dynamic => elf.ET.DYN,
+ },
+ };
+ mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
index += 2;
const machine = self.module.target.cpu.arch.toElfMachine();
@@ -496,10 +499,11 @@ const Update = struct {
mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian);
index += 4;
+ const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?;
+
switch (ptr_width) {
.p32 => {
- // e_entry
- mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.entry_addr.?), endian);
+ mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian);
index += 4;
// e_phoff
@@ -512,7 +516,7 @@ const Update = struct {
},
.p64 => {
// e_entry
- mem.writeInt(u64, hdr_buf[index..][0..8], self.entry_addr.?, endian);
+ mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian);
index += 8;
// e_phoff
@@ -750,7 +754,20 @@ const Update = struct {
/// Truncates the existing file contents and overwrites the contents.
/// Returns an error if `file` is not already open with +read +write +seek abilities.
-pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+ switch (module.output_mode) {
+ .Exe => {},
+ .Obj => {},
+ .Lib => return error.TODOImplementWritingLibFiles,
+ }
+ switch (module.object_format) {
+ .unknown => unreachable, // TODO remove this tag from the enum
+ .coff => return error.TODOImplementWritingCOFF,
+ .elf => {},
+ .macho => return error.TODOImplementWritingMachO,
+ .wasm => return error.TODOImplementWritingWasmObjects,
+ }
+
var update = Update{
.file = file,
.module = &module,
@@ -778,7 +795,7 @@ pub fn writeExecutableFile(allocator: *Allocator, module: ir.Module, file: fs.Fi
}
/// Returns error.IncrFailed if incremental update could not be performed.
-fn updateExecutableFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
+fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
//var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
// TODO implement incremental linking
@@ -822,3 +839,19 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
.sh_entsize = @intCast(u32, shdr.sh_entsize),
};
}
+
+fn determineMode(module: ir.Module) fs.File.Mode {
+ // On common systems with a 0o022 umask, 0o777 will still result in a file created
+ // with 0o755 permissions, but it works appropriately if the system is configured
+ // more leniently. As another data point, C's fopen seems to open files with the
+ // 666 mode.
+ const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
+ switch (module.output_mode) {
+ .Lib => return switch (module.link_mode) {
+ .Dynamic => executable_mode,
+ .Static => fs.File.default_mode,
+ },
+ .Exe => return executable_mode,
+ .Obj => return fs.File.default_mode,
+ }
+}
diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig
index 9038b8128d..5868f9383d 100644
--- a/src-self-hosted/test.zig
+++ b/src-self-hosted/test.zig
@@ -1,237 +1,248 @@
const std = @import("std");
-const mem = std.mem;
-const Target = std.Target;
-const Compilation = @import("compilation.zig").Compilation;
-const introspect = @import("introspect.zig");
-const testing = std.testing;
-const errmsg = @import("errmsg.zig");
-const ZigCompiler = @import("compilation.zig").ZigCompiler;
+const link = @import("link.zig");
+const ir = @import("ir.zig");
+const Allocator = std.mem.Allocator;
-var ctx: TestContext = undefined;
+var global_ctx: TestContext = undefined;
-test "stage2" {
- // TODO provide a way to run tests in evented I/O mode
- if (!std.io.is_async) return error.SkipZigTest;
+test "self-hosted" {
+ try global_ctx.init();
+ defer global_ctx.deinit();
- // TODO https://github.com/ziglang/zig/issues/1364
- // TODO https://github.com/ziglang/zig/issues/3117
- if (true) return error.SkipZigTest;
+ try @import("stage2_tests").addCases(&global_ctx);
- try ctx.init();
- defer ctx.deinit();
-
- try @import("stage2_tests").addCases(&ctx);
-
- try ctx.run();
+ try global_ctx.run();
}
-const file1 = "1.zig";
-// TODO https://github.com/ziglang/zig/issues/3783
-const allocator = std.heap.page_allocator;
-
pub const TestContext = struct {
- zig_compiler: ZigCompiler,
- zig_lib_dir: []u8,
- file_index: std.atomic.Int(usize),
- group: std.event.Group(anyerror!void),
- any_err: anyerror!void,
+ zir_cmp_output_cases: std.ArrayList(ZIRCompareOutputCase),
+ zir_transform_cases: std.ArrayList(ZIRTransformCase),
+
+ pub const ZIRCompareOutputCase = struct {
+ name: []const u8,
+ src: [:0]const u8,
+ expected_stdout: []const u8,
+ };
+
+ pub const ZIRTransformCase = struct {
+ name: []const u8,
+ src: [:0]const u8,
+ expected_zir: []const u8,
+ };
+
+ pub fn addZIRCompareOutput(
+ ctx: *TestContext,
+ name: []const u8,
+ src: [:0]const u8,
+ expected_stdout: []const u8,
+ ) void {
+ ctx.zir_cmp_output_cases.append(.{
+ .name = name,
+ .src = src,
+ .expected_stdout = expected_stdout,
+ }) catch unreachable;
+ }
- const tmp_dir_name = "stage2_test_tmp";
+ pub fn addZIRTransform(
+ ctx: *TestContext,
+ name: []const u8,
+ src: [:0]const u8,
+ expected_zir: []const u8,
+ ) void {
+ ctx.zir_transform_cases.append(.{
+ .name = name,
+ .src = src,
+ .expected_zir = expected_zir,
+ }) catch unreachable;
+ }
fn init(self: *TestContext) !void {
- self.* = TestContext{
- .any_err = {},
- .zig_compiler = undefined,
- .zig_lib_dir = undefined,
- .group = undefined,
- .file_index = std.atomic.Int(usize).init(0),
+ self.* = .{
+ .zir_cmp_output_cases = std.ArrayList(ZIRCompareOutputCase).init(std.heap.page_allocator),
+ .zir_transform_cases = std.ArrayList(ZIRTransformCase).init(std.heap.page_allocator),
};
-
- self.zig_compiler = try ZigCompiler.init(allocator);
- errdefer self.zig_compiler.deinit();
-
- self.group = std.event.Group(anyerror!void).init(allocator);
- errdefer self.group.wait() catch {};
-
- self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
- errdefer allocator.free(self.zig_lib_dir);
-
- try std.fs.cwd().makePath(tmp_dir_name);
- errdefer std.fs.cwd().deleteTree(tmp_dir_name) catch {};
}
fn deinit(self: *TestContext) void {
- std.fs.cwd().deleteTree(tmp_dir_name) catch {};
- allocator.free(self.zig_lib_dir);
- self.zig_compiler.deinit();
+ self.zir_cmp_output_cases.deinit();
+ self.zir_transform_cases.deinit();
+ self.* = undefined;
}
fn run(self: *TestContext) !void {
- std.event.Loop.startCpuBoundOperation();
- self.any_err = self.group.wait();
- return self.any_err;
+ var progress = std.Progress{};
+ const root_node = try progress.start("zir", self.zir_cmp_output_cases.items.len +
+ self.zir_transform_cases.items.len);
+ defer root_node.end();
+
+ const native_info = try std.zig.system.NativeTargetInfo.detect(std.heap.page_allocator, .{});
+
+ for (self.zir_cmp_output_cases.items) |case| {
+ std.testing.base_allocator_instance.reset();
+ try self.runOneZIRCmpOutputCase(std.testing.allocator, root_node, case, native_info.target);
+ try std.testing.allocator_instance.validate();
+ }
+ for (self.zir_transform_cases.items) |case| {
+ std.testing.base_allocator_instance.reset();
+ try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, native_info.target);
+ try std.testing.allocator_instance.validate();
+ }
}
- fn testCompileError(
+ fn runOneZIRCmpOutputCase(
self: *TestContext,
- source: []const u8,
- path: []const u8,
- line: usize,
- column: usize,
- msg: []const u8,
+ allocator: *Allocator,
+ root_node: *std.Progress.Node,
+ case: ZIRCompareOutputCase,
+ target: std.Target,
) !void {
- var file_index_buf: [20]u8 = undefined;
- const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", .{self.file_index.incr()});
- const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
+ var tmp = std.testing.tmpDir(.{ .share_with_child_process = true });
+ defer tmp.cleanup();
- if (std.fs.path.dirname(file1_path)) |dirname| {
- try std.fs.cwd().makePath(dirname);
- }
+ var prg_node = root_node.start(case.name, 4);
+ prg_node.activate();
+ defer prg_node.end();
- try std.fs.cwd().writeFile(file1_path, source);
+ var zir_module = x: {
+ var parse_node = prg_node.start("parse", null);
+ parse_node.activate();
+ defer parse_node.end();
- var comp = try Compilation.create(
- &self.zig_compiler,
- "test",
- file1_path,
- .Native,
- .Obj,
- .Debug,
- true, // is_static
- self.zig_lib_dir,
- );
- errdefer comp.destroy();
-
- comp.start();
+ break :x try ir.text.parse(allocator, case.src);
+ };
+ defer zir_module.deinit(allocator);
+ if (zir_module.errors.len != 0) {
+ debugPrintErrors(case.src, zir_module.errors);
+ return error.ParseFailure;
+ }
- try self.group.call(getModuleEvent, comp, source, path, line, column, msg);
- }
+ var analyzed_module = x: {
+ var analyze_node = prg_node.start("analyze", null);
+ analyze_node.activate();
+ defer analyze_node.end();
+
+ break :x try ir.analyze(allocator, zir_module, .{
+ .target = target,
+ .output_mode = .Exe,
+ .link_mode = .Static,
+ .optimize_mode = .Debug,
+ });
+ };
+ defer analyzed_module.deinit(allocator);
+ if (analyzed_module.errors.len != 0) {
+ debugPrintErrors(case.src, analyzed_module.errors);
+ return error.ParseFailure;
+ }
- fn testCompareOutputLibC(
- self: *TestContext,
- source: []const u8,
- expected_output: []const u8,
- ) !void {
- var file_index_buf: [20]u8 = undefined;
- const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", .{self.file_index.incr()});
- const file1_path = try std.fs.path.join(allocator, [_][]const u8{ tmp_dir_name, file_index, file1 });
+ var link_result = x: {
+ var link_node = prg_node.start("link", null);
+ link_node.activate();
+ defer link_node.end();
- const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", .{ file1_path, (Target{ .Native = {} }).exeFileExt() });
- if (std.fs.path.dirname(file1_path)) |dirname| {
- try std.fs.cwd().makePath(dirname);
+ break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out");
+ };
+ defer link_result.deinit(allocator);
+ if (link_result.errors.len != 0) {
+ debugPrintErrors(case.src, link_result.errors);
+ return error.LinkFailure;
}
- try std.fs.cwd().writeFile(file1_path, source);
-
- var comp = try Compilation.create(
- &self.zig_compiler,
- "test",
- file1_path,
- .Native,
- .Exe,
- .Debug,
- false,
- self.zig_lib_dir,
- );
- errdefer comp.destroy();
-
- _ = try comp.addLinkLib("c", true);
- comp.link_out_file = output_file;
- comp.start();
-
- try self.group.call(getModuleEventSuccess, comp, output_file, expected_output);
- }
+ var exec_result = x: {
+ var exec_node = prg_node.start("execute", null);
+ exec_node.activate();
+ defer exec_node.end();
- async fn getModuleEventSuccess(
- comp: *Compilation,
- exe_file: []const u8,
- expected_output: []const u8,
- ) anyerror!void {
- defer comp.destroy();
- const build_event = comp.events.get();
-
- switch (build_event) {
- .Ok => {
- const argv = [_][]const u8{exe_file};
- // TODO use event loop
- const child = try std.ChildProcess.exec(.{
- .allocator = allocator,
- .argv = argv,
- .max_output_bytes = 1024 * 1024,
- });
- switch (child.term) {
- .Exited => |code| {
- if (code != 0) {
- return error.BadReturnCode;
- }
- },
- else => {
- return error.Crashed;
- },
- }
- if (!mem.eql(u8, child.stdout, expected_output)) {
- return error.OutputMismatch;
- }
- },
- .Error => @panic("Cannot return error: https://github.com/ziglang/zig/issues/3190"), // |err| return err,
- .Fail => |msgs| {
- const stderr = std.io.getStdErr();
- try stderr.write("build incorrectly failed:\n");
- for (msgs) |msg| {
- defer msg.destroy();
- try msg.printToFile(stderr, .Auto);
+ break :x try std.ChildProcess.exec(.{
+ .allocator = allocator,
+ .argv = &[_][]const u8{"./a.out"},
+ .cwd_dir = tmp.dir,
+ });
+ };
+ defer allocator.free(exec_result.stdout);
+ defer allocator.free(exec_result.stderr);
+ switch (exec_result.term) {
+ .Exited => |code| {
+ if (code != 0) {
+ std.debug.warn("elf file exited with code {}\n", .{code});
+ return error.BinaryBadExitCode;
}
},
+ else => return error.BinaryCrashed,
}
+ std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout);
}
- async fn getModuleEvent(
- comp: *Compilation,
- source: []const u8,
- path: []const u8,
- line: usize,
- column: usize,
- text: []const u8,
- ) anyerror!void {
- defer comp.destroy();
- const build_event = comp.events.get();
-
- switch (build_event) {
- .Ok => {
- @panic("build incorrectly succeeded");
- },
- .Error => |err| {
- @panic("build incorrectly failed");
- },
- .Fail => |msgs| {
- testing.expect(msgs.len != 0);
- for (msgs) |msg| {
- if (mem.endsWith(u8, msg.realpath, path) and mem.eql(u8, msg.text, text)) {
- const span = msg.getSpan();
- const first_token = msg.getTree().tokens.at(span.first);
- const last_token = msg.getTree().tokens.at(span.first);
- const start_loc = msg.getTree().tokenLocationPtr(0, first_token);
- if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
- return;
- }
- }
- }
- std.debug.warn("\n=====source:=======\n{}\n====expected:========\n{}:{}:{}: error: {}\n", .{
- source,
- path,
- line,
- column,
- text,
- });
- std.debug.warn("\n====found:========\n", .{});
- const stderr = std.io.getStdErr();
- for (msgs) |msg| {
- defer msg.destroy();
- try msg.printToFile(stderr, errmsg.Color.Auto);
- }
- std.debug.warn("============\n", .{});
- return error.TestFailed;
- },
+ fn runOneZIRTransformCase(
+ self: *TestContext,
+ allocator: *Allocator,
+ root_node: *std.Progress.Node,
+ case: ZIRTransformCase,
+ target: std.Target,
+ ) !void {
+ var prg_node = root_node.start(case.name, 4);
+ prg_node.activate();
+ defer prg_node.end();
+
+ var parse_node = prg_node.start("parse", null);
+ parse_node.activate();
+ var zir_module = try ir.text.parse(allocator, case.src);
+ defer zir_module.deinit(allocator);
+ if (zir_module.errors.len != 0) {
+ debugPrintErrors(case.src, zir_module.errors);
+ return error.ParseFailure;
}
+ parse_node.end();
+
+ var analyze_node = prg_node.start("analyze", null);
+ analyze_node.activate();
+ var analyzed_module = try ir.analyze(allocator, zir_module, .{
+ .target = target,
+ .output_mode = .Obj,
+ .link_mode = .Static,
+ .optimize_mode = .Debug,
+ });
+ defer analyzed_module.deinit(allocator);
+ if (analyzed_module.errors.len != 0) {
+ debugPrintErrors(case.src, analyzed_module.errors);
+ return error.ParseFailure;
+ }
+ analyze_node.end();
+
+ var emit_node = prg_node.start("emit", null);
+ emit_node.activate();
+ var new_zir_module = try ir.text.emit_zir(allocator, analyzed_module);
+ defer new_zir_module.deinit(allocator);
+ emit_node.end();
+
+ var write_node = prg_node.start("write", null);
+ write_node.activate();
+ var out_zir = std.ArrayList(u8).init(allocator);
+ defer out_zir.deinit();
+ try new_zir_module.writeToStream(allocator, out_zir.outStream());
+ write_node.end();
+
+ std.testing.expectEqualSlices(u8, case.expected_zir, out_zir.items);
}
};
+
+fn debugPrintErrors(src: []const u8, errors: var) void {
+ std.debug.warn("\n", .{});
+ var nl = true;
+ var line: usize = 1;
+ for (src) |byte| {
+ if (nl) {
+ std.debug.warn("{: >3}| ", .{line});
+ nl = false;
+ }
+ if (byte == '\n') {
+ nl = true;
+ line += 1;
+ }
+ std.debug.warn("{c}", .{byte});
+ }
+ std.debug.warn("\n", .{});
+ for (errors) |err_msg| {
+ const loc = std.zig.findLineColumn(src, err_msg.byte_offset);
+ std.debug.warn("{}:{}: error: {}\n", .{ loc.line + 1, loc.column + 1, err_msg.msg });
+ }
+}
diff --git a/src-self-hosted/translate_c.zig b/src-self-hosted/translate_c.zig
index bda152f134..1c689e9f76 100644
--- a/src-self-hosted/translate_c.zig
+++ b/src-self-hosted/translate_c.zig
@@ -3913,18 +3913,20 @@ fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node {
};
var aps_int = int;
const is_negative = ZigClangAPSInt_isSigned(int) and ZigClangAPSInt_isNegative(int);
- if (is_negative)
- aps_int = ZigClangAPSInt_negate(aps_int);
- var big = try math.big.Int.initCapacity(c.a(), num_limbs);
- if (is_negative)
- big.negate();
- defer big.deinit();
+ if (is_negative) aps_int = ZigClangAPSInt_negate(aps_int);
+ defer if (is_negative) {
+ ZigClangAPSInt_free(aps_int);
+ };
+
+ const limbs = try c.a().alloc(math.big.Limb, num_limbs);
+ defer c.a().free(limbs);
+
const data = ZigClangAPSInt_getRawData(aps_int);
- switch (@sizeOf(std.math.big.Limb)) {
+ switch (@sizeOf(math.big.Limb)) {
8 => {
var i: usize = 0;
while (i < num_limbs) : (i += 1) {
- big.limbs[i] = data[i];
+ limbs[i] = data[i];
}
},
4 => {
@@ -3934,23 +3936,23 @@ fn transCreateNodeAPInt(c: *Context, int: *const ZigClangAPSInt) !*ast.Node {
limb_i += 2;
data_i += 1;
}) {
- big.limbs[limb_i] = @truncate(u32, data[data_i]);
- big.limbs[limb_i + 1] = @truncate(u32, data[data_i] >> 32);
+ limbs[limb_i] = @truncate(u32, data[data_i]);
+ limbs[limb_i + 1] = @truncate(u32, data[data_i] >> 32);
}
},
else => @compileError("unimplemented"),
}
- const str = big.toString(c.a(), 10, false) catch |err| switch (err) {
+
+ const big: math.big.int.Const = .{ .limbs = limbs, .positive = !is_negative };
+ const str = big.toStringAlloc(c.a(), 10, false) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
- else => unreachable,
};
+ defer c.a().free(str);
const token = try appendToken(c, .IntegerLiteral, str);
const node = try c.a().create(ast.Node.IntegerLiteral);
node.* = .{
.token = token,
};
- if (is_negative)
- ZigClangAPSInt_free(aps_int);
return &node.base;
}
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
index 51fe8cc685..25f726a680 100644
--- a/src-self-hosted/type.zig
+++ b/src-self-hosted/type.zig
@@ -20,37 +20,40 @@ pub const Type = extern union {
pub fn zigTypeTag(self: Type) std.builtin.TypeId {
switch (self.tag()) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .int_signed,
+ .int_unsigned,
=> return .Int,
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
+ .f16,
+ .f32,
+ .f64,
+ .f128,
=> return .Float,
- .@"c_void" => return .Opaque,
- .@"bool" => return .Bool,
- .@"void" => return .Void,
- .@"type" => return .Type,
- .@"anyerror" => return .ErrorSet,
- .@"comptime_int" => return .ComptimeInt,
- .@"comptime_float" => return .ComptimeFloat,
- .@"noreturn" => return .NoReturn,
+ .c_void => return .Opaque,
+ .bool => return .Bool,
+ .void => return .Void,
+ .type => return .Type,
+ .anyerror => return .ErrorSet,
+ .comptime_int => return .ComptimeInt,
+ .comptime_float => return .ComptimeFloat,
+ .noreturn => return .NoReturn,
.fn_naked_noreturn_no_args => return .Fn,
+ .fn_ccc_void_no_args => return .Fn,
.array, .array_u8_sentinel_0 => return .Array,
.single_const_pointer => return .Pointer,
@@ -153,35 +156,36 @@ pub const Type = extern union {
while (true) {
const t = ty.tag();
switch (t) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
- .@"c_void",
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .c_void,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
=> return out_stream.writeAll(@tagName(t)),
.const_slice_u8 => return out_stream.writeAll("[]const u8"),
.fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
+ .fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"),
.single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"),
.array_u8_sentinel_0 => {
@@ -200,6 +204,14 @@ pub const Type = extern union {
ty = payload.pointee_type;
continue;
},
+ .int_signed => {
+ const payload = @fieldParentPtr(Payload.IntSigned, "base", ty.ptr_otherwise);
+ return out_stream.print("i{}", .{payload.bits});
+ },
+ .int_unsigned => {
+ const payload = @fieldParentPtr(Payload.IntUnsigned, "base", ty.ptr_otherwise);
+ return out_stream.print("u{}", .{payload.bits});
+ },
}
unreachable;
}
@@ -207,32 +219,33 @@ pub const Type = extern union {
pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value {
switch (self.tag()) {
- .@"u8" => return Value.initTag(.u8_type),
- .@"i8" => return Value.initTag(.i8_type),
- .@"isize" => return Value.initTag(.isize_type),
- .@"usize" => return Value.initTag(.usize_type),
- .@"c_short" => return Value.initTag(.c_short_type),
- .@"c_ushort" => return Value.initTag(.c_ushort_type),
- .@"c_int" => return Value.initTag(.c_int_type),
- .@"c_uint" => return Value.initTag(.c_uint_type),
- .@"c_long" => return Value.initTag(.c_long_type),
- .@"c_ulong" => return Value.initTag(.c_ulong_type),
- .@"c_longlong" => return Value.initTag(.c_longlong_type),
- .@"c_ulonglong" => return Value.initTag(.c_ulonglong_type),
- .@"c_longdouble" => return Value.initTag(.c_longdouble_type),
- .@"c_void" => return Value.initTag(.c_void_type),
- .@"f16" => return Value.initTag(.f16_type),
- .@"f32" => return Value.initTag(.f32_type),
- .@"f64" => return Value.initTag(.f64_type),
- .@"f128" => return Value.initTag(.f128_type),
- .@"bool" => return Value.initTag(.bool_type),
- .@"void" => return Value.initTag(.void_type),
- .@"type" => return Value.initTag(.type_type),
- .@"anyerror" => return Value.initTag(.anyerror_type),
- .@"comptime_int" => return Value.initTag(.comptime_int_type),
- .@"comptime_float" => return Value.initTag(.comptime_float_type),
- .@"noreturn" => return Value.initTag(.noreturn_type),
+ .u8 => return Value.initTag(.u8_type),
+ .i8 => return Value.initTag(.i8_type),
+ .isize => return Value.initTag(.isize_type),
+ .usize => return Value.initTag(.usize_type),
+ .c_short => return Value.initTag(.c_short_type),
+ .c_ushort => return Value.initTag(.c_ushort_type),
+ .c_int => return Value.initTag(.c_int_type),
+ .c_uint => return Value.initTag(.c_uint_type),
+ .c_long => return Value.initTag(.c_long_type),
+ .c_ulong => return Value.initTag(.c_ulong_type),
+ .c_longlong => return Value.initTag(.c_longlong_type),
+ .c_ulonglong => return Value.initTag(.c_ulonglong_type),
+ .c_longdouble => return Value.initTag(.c_longdouble_type),
+ .c_void => return Value.initTag(.c_void_type),
+ .f16 => return Value.initTag(.f16_type),
+ .f32 => return Value.initTag(.f32_type),
+ .f64 => return Value.initTag(.f64_type),
+ .f128 => return Value.initTag(.f128_type),
+ .bool => return Value.initTag(.bool_type),
+ .void => return Value.initTag(.void_type),
+ .type => return Value.initTag(.type_type),
+ .anyerror => return Value.initTag(.anyerror_type),
+ .comptime_int => return Value.initTag(.comptime_int_type),
+ .comptime_float => return Value.initTag(.comptime_float_type),
+ .noreturn => return Value.initTag(.noreturn_type),
.fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type),
+ .fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type),
.single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type),
.const_slice_u8 => return Value.initTag(.const_slice_u8_type),
else => {
@@ -245,35 +258,38 @@ pub const Type = extern union {
pub fn isSinglePointer(self: Type) bool {
return switch (self.tag()) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"c_void",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
.array,
.array_u8_sentinel_0,
.const_slice_u8,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .int_unsigned,
+ .int_signed,
=> false,
.single_const_pointer,
@@ -284,36 +300,39 @@ pub const Type = extern union {
pub fn isSlice(self: Type) bool {
return switch (self.tag()) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"c_void",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
.array,
.array_u8_sentinel_0,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .int_unsigned,
+ .int_signed,
=> false,
.const_slice_u8 => true,
@@ -323,34 +342,37 @@ pub const Type = extern union {
/// Asserts the type is a pointer type.
pub fn pointerIsConst(self: Type) bool {
return switch (self.tag()) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"c_void",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
.array,
.array_u8_sentinel_0,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
.single_const_pointer,
@@ -363,32 +385,35 @@ pub const Type = extern union {
/// Asserts the type is a pointer or array type.
pub fn elemType(self: Type) Type {
return switch (self.tag()) {
- .@"u8",
- .@"i8",
- .@"isize",
- .@"usize",
- .@"c_short",
- .@"c_ushort",
- .@"c_int",
- .@"c_uint",
- .@"c_long",
- .@"c_ulong",
- .@"c_longlong",
- .@"c_ulonglong",
- .@"c_longdouble",
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"c_void",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
.array => self.cast(Payload.Array).?.elem_type,
@@ -398,7 +423,7 @@ pub const Type = extern union {
};
}
- /// Asserts the type is an array.
+ /// Asserts the type is an array or vector.
pub fn arrayLen(self: Type) u64 {
return switch (self.tag()) {
.u8,
@@ -427,9 +452,12 @@ pub const Type = extern union {
.comptime_float,
.noreturn,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
.array => self.cast(Payload.Array).?.len,
@@ -437,23 +465,67 @@ pub const Type = extern union {
};
}
+ /// Returns true if and only if the type is a fixed-width, signed integer.
+ pub fn isSignedInt(self: Type) bool {
+ return switch (self.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .int_unsigned,
+ .u8,
+ .usize,
+ .c_ushort,
+ .c_uint,
+ .c_ulong,
+ .c_ulonglong,
+ => false,
+
+ .int_signed,
+ .i8,
+ .isize,
+ .c_short,
+ .c_int,
+ .c_long,
+ .c_longlong,
+ => true,
+ };
+ }
+
/// Asserts the type is a fixed-width integer.
pub fn intInfo(self: Type, target: Target) struct { signed: bool, bits: u16 } {
return switch (self.tag()) {
- .@"f16",
- .@"f32",
- .@"f64",
- .@"f128",
- .@"c_longdouble",
- .@"c_void",
- .@"bool",
- .@"void",
- .@"type",
- .@"anyerror",
- .@"comptime_int",
- .@"comptime_float",
- .@"noreturn",
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
.fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
.array,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@@ -461,18 +533,46 @@ pub const Type = extern union {
.const_slice_u8,
=> unreachable,
- .@"u8" => .{ .signed = false, .bits = 8 },
- .@"i8" => .{ .signed = true, .bits = 8 },
- .@"usize" => .{ .signed = false, .bits = target.cpu.arch.ptrBitWidth() },
- .@"isize" => .{ .signed = true, .bits = target.cpu.arch.ptrBitWidth() },
- .@"c_short" => .{ .signed = true, .bits = CInteger.short.sizeInBits(target) },
- .@"c_ushort" => .{ .signed = false, .bits = CInteger.ushort.sizeInBits(target) },
- .@"c_int" => .{ .signed = true, .bits = CInteger.int.sizeInBits(target) },
- .@"c_uint" => .{ .signed = false, .bits = CInteger.uint.sizeInBits(target) },
- .@"c_long" => .{ .signed = true, .bits = CInteger.long.sizeInBits(target) },
- .@"c_ulong" => .{ .signed = false, .bits = CInteger.ulong.sizeInBits(target) },
- .@"c_longlong" => .{ .signed = true, .bits = CInteger.longlong.sizeInBits(target) },
- .@"c_ulonglong" => .{ .signed = false, .bits = CInteger.ulonglong.sizeInBits(target) },
+ .int_unsigned => .{ .signed = false, .bits = self.cast(Payload.IntUnsigned).?.bits },
+ .int_signed => .{ .signed = true, .bits = self.cast(Payload.IntSigned).?.bits },
+ .u8 => .{ .signed = false, .bits = 8 },
+ .i8 => .{ .signed = true, .bits = 8 },
+ .usize => .{ .signed = false, .bits = target.cpu.arch.ptrBitWidth() },
+ .isize => .{ .signed = true, .bits = target.cpu.arch.ptrBitWidth() },
+ .c_short => .{ .signed = true, .bits = CType.short.sizeInBits(target) },
+ .c_ushort => .{ .signed = false, .bits = CType.ushort.sizeInBits(target) },
+ .c_int => .{ .signed = true, .bits = CType.int.sizeInBits(target) },
+ .c_uint => .{ .signed = false, .bits = CType.uint.sizeInBits(target) },
+ .c_long => .{ .signed = true, .bits = CType.long.sizeInBits(target) },
+ .c_ulong => .{ .signed = false, .bits = CType.ulong.sizeInBits(target) },
+ .c_longlong => .{ .signed = true, .bits = CType.longlong.sizeInBits(target) },
+ .c_ulonglong => .{ .signed = false, .bits = CType.ulonglong.sizeInBits(target) },
+ };
+ }
+
+ pub fn isFloat(self: Type) bool {
+ return switch (self.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ => true,
+
+ else => false,
+ };
+ }
+
+ /// Asserts the type is a fixed-size float.
+ pub fn floatBits(self: Type, target: Target) u16 {
+ return switch (self.tag()) {
+ .f16 => 16,
+ .f32 => 32,
+ .f64 => 64,
+ .f128 => 128,
+ .c_longdouble => CType.longdouble.sizeInBits(target),
+
+ else => unreachable,
};
}
@@ -480,6 +580,7 @@ pub const Type = extern union {
pub fn fnParamLen(self: Type) usize {
return switch (self.tag()) {
.fn_naked_noreturn_no_args => 0,
+ .fn_ccc_void_no_args => 0,
.f16,
.f32,
@@ -511,6 +612,8 @@ pub const Type = extern union {
.c_ulong,
.c_longlong,
.c_ulonglong,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
};
}
@@ -520,6 +623,7 @@ pub const Type = extern union {
pub fn fnParamTypes(self: Type, types: []Type) void {
switch (self.tag()) {
.fn_naked_noreturn_no_args => return,
+ .fn_ccc_void_no_args => return,
.f16,
.f32,
@@ -551,6 +655,8 @@ pub const Type = extern union {
.c_ulong,
.c_longlong,
.c_ulonglong,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
}
}
@@ -559,6 +665,7 @@ pub const Type = extern union {
pub fn fnReturnType(self: Type) Type {
return switch (self.tag()) {
.fn_naked_noreturn_no_args => Type.initTag(.noreturn),
+ .fn_ccc_void_no_args => Type.initTag(.void),
.f16,
.f32,
@@ -590,6 +697,8 @@ pub const Type = extern union {
.c_ulong,
.c_longlong,
.c_ulonglong,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
};
}
@@ -598,6 +707,7 @@ pub const Type = extern union {
pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention {
return switch (self.tag()) {
.fn_naked_noreturn_no_args => .Naked,
+ .fn_ccc_void_no_args => .C,
.f16,
.f32,
@@ -629,10 +739,148 @@ pub const Type = extern union {
.c_ulong,
.c_longlong,
.c_ulonglong,
+ .int_unsigned,
+ .int_signed,
=> unreachable,
};
}
+ pub fn isNumeric(self: Type) bool {
+ return switch (self.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .comptime_int,
+ .comptime_float,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .int_unsigned,
+ .int_signed,
+ => true,
+
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .noreturn,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ => false,
+ };
+ }
+
+ pub fn onePossibleValue(self: Type) bool {
+ var ty = self;
+ while (true) switch (ty.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .comptime_int,
+ .comptime_float,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .bool,
+ .type,
+ .anyerror,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ => return false,
+
+ .c_void,
+ .void,
+ .noreturn,
+ => return true,
+
+ .int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
+ .int_signed => return ty.cast(Payload.IntSigned).?.bits == 0,
+ .array => {
+ const array = ty.cast(Payload.Array).?;
+ if (array.len == 0)
+ return true;
+ ty = array.elem_type;
+ continue;
+ },
+ .single_const_pointer => {
+ const ptr = ty.cast(Payload.SingleConstPointer).?;
+ ty = ptr.pointee_type;
+ continue;
+ },
+ };
+ }
+
+ pub fn isCPtr(self: Type) bool {
+ return switch (self.tag()) {
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .comptime_int,
+ .comptime_float,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .bool,
+ .type,
+ .anyerror,
+ .fn_naked_noreturn_no_args,
+ .fn_ccc_void_no_args,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .c_void,
+ .void,
+ .noreturn,
+ .int_unsigned,
+ .int_signed,
+ .array,
+ .single_const_pointer,
+ => return false,
+ };
+ }
+
/// This enum does not directly correspond to `std.builtin.TypeId` because
/// it has extra enum tags in it, as a way of using less memory. For example,
/// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
@@ -667,6 +915,7 @@ pub const Type = extern union {
comptime_float,
noreturn,
fn_naked_noreturn_no_args,
+ fn_ccc_void_no_args,
single_const_pointer_to_comptime_int,
const_slice_u8, // See last_no_payload_tag below.
// After this, the tag requires a payload.
@@ -674,6 +923,8 @@ pub const Type = extern union {
array_u8_sentinel_0,
array,
single_const_pointer,
+ int_signed,
+ int_unsigned,
pub const last_no_payload_tag = Tag.const_slice_u8;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -700,10 +951,22 @@ pub const Type = extern union {
pointee_type: Type,
};
+
+ pub const IntSigned = struct {
+ base: Payload = Payload{ .tag = .int_signed },
+
+ bits: u16,
+ };
+
+ pub const IntUnsigned = struct {
+ base: Payload = Payload{ .tag = .int_unsigned },
+
+ bits: u16,
+ };
};
};
-pub const CInteger = enum {
+pub const CType = enum {
short,
ushort,
int,
@@ -712,8 +975,9 @@ pub const CInteger = enum {
ulong,
longlong,
ulonglong,
+ longdouble,
- pub fn sizeInBits(self: CInteger, target: Target) u16 {
+ pub fn sizeInBits(self: CType, target: Target) u16 {
const arch = target.cpu.arch;
switch (target.os.tag) {
.freestanding, .other => switch (target.cpu.arch) {
@@ -729,6 +993,7 @@ pub const CInteger = enum {
.longlong,
.ulonglong,
=> return 64,
+ .longdouble => @panic("TODO figure out what kind of float `long double` is on this target"),
},
else => switch (self) {
.short,
@@ -743,6 +1008,7 @@ pub const CInteger = enum {
.longlong,
.ulonglong,
=> return 64,
+ .longdouble => @panic("TODO figure out what kind of float `long double` is on this target"),
},
},
@@ -767,6 +1033,7 @@ pub const CInteger = enum {
.longlong,
.ulonglong,
=> return 64,
+ .longdouble => @panic("TODO figure out what kind of float `long double` is on this target"),
},
.windows, .uefi => switch (self) {
@@ -781,6 +1048,7 @@ pub const CInteger = enum {
.longlong,
.ulonglong,
=> return 64,
+ .longdouble => @panic("TODO figure out what kind of float `long double` is on this target"),
},
.ios => switch (self) {
@@ -795,6 +1063,7 @@ pub const CInteger = enum {
.longlong,
.ulonglong,
=> return 64,
+ .longdouble => @panic("TODO figure out what kind of float `long double` is on this target"),
},
.ananas,
@@ -821,7 +1090,7 @@ pub const CInteger = enum {
.amdpal,
.hermit,
.hurd,
- => @panic("TODO specify the C integer type sizes for this OS"),
+ => @panic("TODO specify the C integer and float type sizes for this OS"),
}
}
};
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
index 2adbb7807e..3d04e6e813 100644
--- a/src-self-hosted/value.zig
+++ b/src-self-hosted/value.zig
@@ -2,7 +2,8 @@ const std = @import("std");
const Type = @import("type.zig").Type;
const log2 = std.math.log2;
const assert = std.debug.assert;
-const BigInt = std.math.big.Int;
+const BigIntConst = std.math.big.int.Const;
+const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
const Allocator = std.mem.Allocator;
@@ -45,12 +46,14 @@ pub const Value = extern union {
comptime_float_type,
noreturn_type,
fn_naked_noreturn_no_args_type,
+ fn_ccc_void_no_args_type,
single_const_pointer_to_comptime_int_type,
const_slice_u8_type,
+ undef,
zero,
- void_value,
- noreturn_value,
+ the_one_possible_value, // when the type only has one possible value
+ null_value,
bool_true,
bool_false, // See last_no_payload_tag below.
// After this, the tag requires a payload.
@@ -58,11 +61,13 @@ pub const Value = extern union {
ty,
int_u64,
int_i64,
- int_big,
+ int_big_positive,
+ int_big_negative,
function,
ref,
ref_val,
bytes,
+ repeated, // the value is a value repeated some number of times
pub const last_no_payload_tag = Tag.bool_false;
pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
@@ -132,18 +137,21 @@ pub const Value = extern union {
.comptime_float_type => return out_stream.writeAll("comptime_float"),
.noreturn_type => return out_stream.writeAll("noreturn"),
.fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
+ .fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
.const_slice_u8_type => return out_stream.writeAll("[]const u8"),
+ .null_value => return out_stream.writeAll("null"),
+ .undef => return out_stream.writeAll("undefined"),
.zero => return out_stream.writeAll("0"),
- .void_value => return out_stream.writeAll("{}"),
- .noreturn_value => return out_stream.writeAll("unreachable"),
+ .the_one_possible_value => return out_stream.writeAll("(one possible value)"),
.bool_true => return out_stream.writeAll("true"),
.bool_false => return out_stream.writeAll("false"),
.ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
.int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
.int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
- .int_big => return out_stream.print("{}", .{val.cast(Payload.IntBig).?.big_int}),
+ .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
+ .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
.function => return out_stream.writeAll("(function)"),
.ref => return out_stream.writeAll("(ref)"),
.ref_val => {
@@ -152,6 +160,10 @@ pub const Value = extern union {
continue;
},
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
+ .repeated => {
+ try out_stream.writeAll("(repeated) ");
+ val = val.cast(Payload.Repeated).?.val;
+ },
};
}
@@ -195,27 +207,31 @@ pub const Value = extern union {
.comptime_float_type => Type.initTag(.@"comptime_float"),
.noreturn_type => Type.initTag(.@"noreturn"),
.fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args),
+ .fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args),
.single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
.const_slice_u8_type => Type.initTag(.const_slice_u8),
+ .undef,
.zero,
- .void_value,
- .noreturn_value,
+ .the_one_possible_value,
.bool_true,
.bool_false,
+ .null_value,
.int_u64,
.int_i64,
- .int_big,
+ .int_big_positive,
+ .int_big_negative,
.function,
.ref,
.ref_val,
.bytes,
+ .repeated,
=> unreachable,
};
}
/// Asserts the value is an integer.
- pub fn toBigInt(self: Value, allocator: *Allocator) Allocator.Error!BigInt {
+ pub fn toBigInt(self: Value, space: *BigIntSpace) BigIntConst {
switch (self.tag()) {
.ty,
.u8_type,
@@ -244,23 +260,28 @@ pub const Value = extern union {
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
- .void_value,
- .noreturn_value,
.bool_true,
.bool_false,
+ .null_value,
.function,
.ref,
.ref_val,
.bytes,
+ .undef,
+ .repeated,
=> unreachable,
- .zero => return BigInt.initSet(allocator, 0),
+ .the_one_possible_value, // An integer with one possible value is always zero.
+ .zero,
+ => return BigIntMutable.init(&space.limbs, 0).toConst(),
- .int_u64 => return BigInt.initSet(allocator, self.cast(Payload.Int_u64).?.int),
- .int_i64 => return BigInt.initSet(allocator, self.cast(Payload.Int_i64).?.int),
- .int_big => return self.cast(Payload.IntBig).?.big_int,
+ .int_u64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_u64).?.int).toConst(),
+ .int_i64 => return BigIntMutable.init(&space.limbs, self.cast(Payload.Int_i64).?.int).toConst(),
+ .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt(),
+ .int_big_negative => return self.cast(Payload.IntBigPositive).?.asBigInt(),
}
}
@@ -294,23 +315,90 @@ pub const Value = extern union {
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
- .void_value,
- .noreturn_value,
.bool_true,
.bool_false,
+ .null_value,
.function,
.ref,
.ref_val,
.bytes,
+ .undef,
+ .repeated,
=> unreachable,
- .zero => return 0,
+ .zero,
+ .the_one_possible_value, // an integer with one possible value is always zero
+ => return 0,
.int_u64 => return self.cast(Payload.Int_u64).?.int,
.int_i64 => return @intCast(u64, self.cast(Payload.Int_u64).?.int),
- .int_big => return self.cast(Payload.IntBig).?.big_int.to(u64) catch unreachable,
+ .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().to(u64) catch unreachable,
+ .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().to(u64) catch unreachable,
+ }
+ }
+
+ /// Asserts the value is an integer and not undefined.
+ /// Returns the number of bits the value requires to represent stored in twos complement form.
+ pub fn intBitCountTwosComp(self: Value) usize {
+ switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .bool_true,
+ .bool_false,
+ .null_value,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ .undef,
+ .repeated,
+ => unreachable,
+
+ .the_one_possible_value, // an integer with one possible value is always zero
+ .zero,
+ => return 0,
+
+ .int_u64 => {
+ const x = self.cast(Payload.Int_u64).?.int;
+ if (x == 0) return 0;
+ return std.math.log2(x) + 1;
+ },
+ .int_i64 => {
+ @panic("TODO implement i64 intBitCountTwosComp");
+ },
+ .int_big_positive => return self.cast(Payload.IntBigPositive).?.asBigInt().bitCountTwosComp(),
+ .int_big_negative => return self.cast(Payload.IntBigNegative).?.asBigInt().bitCountTwosComp(),
}
}
@@ -344,19 +432,23 @@ pub const Value = extern union {
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
- .void_value,
- .noreturn_value,
.bool_true,
.bool_false,
+ .null_value,
.function,
.ref,
.ref_val,
.bytes,
+ .repeated,
=> unreachable,
- .zero => return true,
+ .zero,
+ .undef,
+ .the_one_possible_value, // an integer with one possible value is always zero
+ => return true,
.int_u64 => switch (ty.zigTypeTag()) {
.Int => {
@@ -381,20 +473,171 @@ pub const Value = extern union {
.ComptimeInt => return true,
else => unreachable,
},
- .int_big => switch (ty.zigTypeTag()) {
+ .int_big_positive => switch (ty.zigTypeTag()) {
.Int => {
const info = ty.intInfo(target);
- return self.cast(Payload.IntBig).?.big_int.fitsInTwosComp(info.signed, info.bits);
+ return self.cast(Payload.IntBigPositive).?.asBigInt().fitsInTwosComp(info.signed, info.bits);
},
.ComptimeInt => return true,
else => unreachable,
},
+ .int_big_negative => switch (ty.zigTypeTag()) {
+ .Int => {
+ const info = ty.intInfo(target);
+ return self.cast(Payload.IntBigNegative).?.asBigInt().fitsInTwosComp(info.signed, info.bits);
+ },
+ .ComptimeInt => return true,
+ else => unreachable,
+ },
+ }
+ }
+
+ /// Asserts the value is a float
+ pub fn floatHasFraction(self: Value) bool {
+ return switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .bool_true,
+ .bool_false,
+ .null_value,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ .repeated,
+ .undef,
+ .int_u64,
+ .int_i64,
+ .int_big_positive,
+ .int_big_negative,
+ .the_one_possible_value,
+ => unreachable,
+
+ .zero => false,
+ };
+ }
+
+ pub fn orderAgainstZero(lhs: Value) std.math.Order {
+ switch (lhs.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .bool_true,
+ .bool_false,
+ .null_value,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ .repeated,
+ .undef,
+ => unreachable,
+
+ .zero,
+ .the_one_possible_value, // an integer with one possible value is always zero
+ => return .eq,
+
+ .int_u64 => return std.math.order(lhs.cast(Payload.Int_u64).?.int, 0),
+ .int_i64 => return std.math.order(lhs.cast(Payload.Int_i64).?.int, 0),
+ .int_big_positive => return lhs.cast(Payload.IntBigPositive).?.asBigInt().orderAgainstScalar(0),
+ .int_big_negative => return lhs.cast(Payload.IntBigNegative).?.asBigInt().orderAgainstScalar(0),
}
}
+ /// Asserts the value is comparable.
+ pub fn order(lhs: Value, rhs: Value) std.math.Order {
+ const lhs_tag = lhs.tag();
+ const rhs_tag = lhs.tag();
+ const lhs_is_zero = lhs_tag == .zero or lhs_tag == .the_one_possible_value;
+ const rhs_is_zero = rhs_tag == .zero or rhs_tag == .the_one_possible_value;
+ if (lhs_is_zero) return rhs.orderAgainstZero().invert();
+ if (rhs_is_zero) return lhs.orderAgainstZero();
+
+ // TODO floats
+
+ var lhs_bigint_space: BigIntSpace = undefined;
+ var rhs_bigint_space: BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_bigint_space);
+ const rhs_bigint = rhs.toBigInt(&rhs_bigint_space);
+ return lhs_bigint.order(rhs_bigint);
+ }
+
+ /// Asserts the value is comparable.
+ pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value) bool {
+ return order(lhs, rhs).compare(op);
+ }
+
+ /// Asserts the value is comparable.
+ pub fn compareWithZero(lhs: Value, op: std.math.CompareOperator) bool {
+ return orderAgainstZero(lhs).compare(op);
+ }
+
+ pub fn toBool(self: Value) bool {
+ return switch (self.tag()) {
+ .bool_true => true,
+ .bool_false => false,
+ else => unreachable,
+ };
+ }
+
/// Asserts the value is a pointer and dereferences it.
pub fn pointerDeref(self: Value) Value {
- switch (self.tag()) {
+ return switch (self.tag()) {
.ty,
.u8_type,
.i8_type,
@@ -422,23 +665,27 @@ pub const Value = extern union {
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
- .void_value,
- .noreturn_value,
.bool_true,
.bool_false,
+ .null_value,
.function,
.int_u64,
.int_i64,
- .int_big,
+ .int_big_positive,
+ .int_big_negative,
.bytes,
+ .undef,
+ .repeated,
=> unreachable,
- .ref => return self.cast(Payload.Ref).?.cell.contents,
- .ref_val => return self.cast(Payload.RefVal).?.val,
- }
+ .the_one_possible_value => Value.initTag(.the_one_possible_value),
+ .ref => self.cast(Payload.Ref).?.cell.contents,
+ .ref_val => self.cast(Payload.RefVal).?.val,
+ };
}
/// Asserts the value is a single-item pointer to an array, or an array,
@@ -472,17 +719,20 @@ pub const Value = extern union {
.comptime_float_type,
.noreturn_type,
.fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
.const_slice_u8_type,
.zero,
- .void_value,
- .noreturn_value,
+ .the_one_possible_value,
.bool_true,
.bool_false,
+ .null_value,
.function,
.int_u64,
.int_i64,
- .int_big,
+ .int_big_positive,
+ .int_big_negative,
+ .undef,
=> unreachable,
.ref => @panic("TODO figure out how MemoryCell works"),
@@ -493,9 +743,70 @@ pub const Value = extern union {
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
return Value.initPayload(&int_payload.base);
},
+
+ // No matter the index; all the elements are the same!
+ .repeated => return self.cast(Payload.Repeated).?.val,
}
}
+ pub fn isUndef(self: Value) bool {
+ return self.tag() == .undef;
+ }
+
+ /// Valid for all types. Asserts the value is not undefined.
+ /// `.the_one_possible_value` is reported as not null.
+ pub fn isNull(self: Value) bool {
+ return switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .fn_ccc_void_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .zero,
+ .the_one_possible_value,
+ .bool_true,
+ .bool_false,
+ .function,
+ .int_u64,
+ .int_i64,
+ .int_big_positive,
+ .int_big_negative,
+ .ref,
+ .ref_val,
+ .bytes,
+ .repeated,
+ => false,
+
+ .undef => unreachable,
+ .null_value => true,
+ };
+ }
+
/// This type is not copyable since it may contain pointers to its inner data.
pub const Payload = struct {
tag: Tag,
@@ -510,9 +821,22 @@ pub const Value = extern union {
int: i64,
};
- pub const IntBig = struct {
- base: Payload = Payload{ .tag = .int_big },
- big_int: BigInt,
+ pub const IntBigPositive = struct {
+ base: Payload = Payload{ .tag = .int_big_positive },
+ limbs: []const std.math.big.Limb,
+
+ pub fn asBigInt(self: IntBigPositive) BigIntConst {
+ return BigIntConst{ .limbs = self.limbs, .positive = true };
+ }
+ };
+
+ pub const IntBigNegative = struct {
+ base: Payload = Payload{ .tag = .int_big_negative },
+ limbs: []const std.math.big.Limb,
+
+ pub fn asBigInt(self: IntBigNegative) BigIntConst {
+ return BigIntConst{ .limbs = self.limbs, .positive = false };
+ }
};
pub const Function = struct {
@@ -550,6 +874,20 @@ pub const Value = extern union {
base: Payload = Payload{ .tag = .ty },
ty: Type,
};
+
+ pub const Repeated = struct {
+ base: Payload = Payload{ .tag = .ty },
+ /// This value is repeated some number of times. The amount of times to repeat
+ /// is stored externally.
+ val: Value,
+ };
+ };
+
+ /// Big enough to fit any non-BigInt value
+ pub const BigIntSpace = struct {
+ /// The +1 is headroom so that operations such as incrementing once or decrementing once
+ /// are possible without using an allocator.
+ limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
};
};