From 31a59c229cb39b9ffd1ee3397a1ce87c36b91477 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jul 2021 19:12:34 -0700 Subject: stage2: improvements towards `zig test` * Add AIR instruction: struct_field_val - This is part of an effort to eliminate the AIR instruction `ref`. - It's implemented for C backend and LLVM backend so far. * Rename `resolvePossiblyUndefinedValue` to `resolveMaybeUndefVal` just to save some columns on long lines. * Sema: add `fieldVal` alongside `fieldPtr` (renamed from `namedFieldPtr`). This is part of an effort to eliminate the AIR instruction `ref`. The idea is to avoid unnecessary loads, stores, stack usage, and IR instructions, by paying a DRY cost. LLVM backend improvements: * internal linkage vs exported linkage is implemented, along with aliases. There is an issue with incremental updates due to missing LLVM API for deleting aliases; see the relevant comment in this commit. - `updateDeclExports` is hooked up to the LLVM backend now. * Fix usage of `Type.tag() == .noreturn` rather than calling `isNoReturn()`. * Properly mark global variables as mutable/constant. * Fix llvm type generation of function pointers * Fix codegen for calls of function pointers * Implement llvm type generation of error unions and error sets. * Implement AIR instructions: addwrap, subwrap, mul, mulwrap, div, bit_and, bool_and, bit_or, bool_or, xor, struct_field_ptr, struct_field_val, unwrap_errunion_err, add for floats, sub for floats. After this commit, `zig test` on a file with `test "example" {}` correctly generates and executes a test binary. However the `test_functions` slice is undefined and just happens to be going into the .bss section, causing the length to be 0. The next step towards `zig test` will be replacing the `test_functions` Decl Value with the set of test function pointers, before it is sent to linker/codegen. --- src/codegen.zig | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/codegen.zig') diff --git a/src/codegen.zig b/src/codegen.zig index c2504f26a8..4924b68ca3 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -851,6 +851,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .ret => try self.airRet(inst), .store => try self.airStore(inst), .struct_field_ptr=> try self.airStructFieldPtr(inst), + .struct_field_val=> try self.airStructFieldVal(inst), .switch_br => try self.airSwitch(inst), .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSlicePtr(inst), @@ -1501,6 +1502,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); } + fn airStructFieldVal(self: *Self, inst: Air.Inst.Index) !void { + const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; + const extra = self.air.extraData(Air.StructField, ty_pl.payload).data; + _ = extra; + return self.fail("TODO implement codegen struct_field_val", .{}); + //return self.finishAir(inst, result, .{ extra.struct_ptr, .none, .none }); + } + fn armOperandShouldBeRegister(self: *Self, mcv: MCValue) !bool { return switch (mcv) { .none => unreachable, -- cgit v1.2.3 From dc88864c9742029c2980fc16cd2c9e6f04ff3568 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jul 2021 17:08:37 -0700 Subject: stage2: implement `@boolToInt` This is the first commit in which some behavior tests are passing for both stage1 and stage2. --- doc/langref.html.in | 4 ++-- src/Air.zig | 6 ++++++ src/AstGen.zig | 2 ++ src/Liveness.zig | 1 + src/Sema.zig | 11 +++++++++-- src/Zir.zig | 5 +++++ src/codegen.zig | 8 ++++++++ src/codegen/c.zig | 15 +++++++++++++++ src/codegen/llvm.zig | 10 ++++++++++ src/link/Coff.zig | 7 +++++-- src/link/Elf.zig | 7 ++++--- src/print_air.zig | 1 + src/type.zig | 17 +++++++++++++++-- src/value.zig | 7 +++++++ test/behavior.zig | 7 ++----- 15 files changed, 92 insertions(+), 16 deletions(-) (limited to 'src/codegen.zig') diff --git a/doc/langref.html.in b/doc/langref.html.in index 38b91468e2..4efa7d0e3c 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -7165,8 +7165,8 @@ fn func(y: *i32) void { {#header_open|@boolToInt#}
{#syntax#}@boolToInt(value: bool) u1{#endsyntax#}

- Converts {#syntax#}true{#endsyntax#} to {#syntax#}u1(1){#endsyntax#} and {#syntax#}false{#endsyntax#} to - {#syntax#}u1(0){#endsyntax#}. + Converts {#syntax#}true{#endsyntax#} to {#syntax#}@as(u1, 1){#endsyntax#} and {#syntax#}false{#endsyntax#} to + {#syntax#}@as(u1, 0){#endsyntax#}.

If the value is known at compile-time, the return type is {#syntax#}comptime_int{#endsyntax#} diff --git a/src/Air.zig b/src/Air.zig index 8cb7b943e3..bd7b3af733 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -189,6 +189,10 @@ pub const Inst = struct { /// Converts a pointer to its address. Result type is always `usize`. /// Uses the `un_op` field. ptrtoint, + /// Given a boolean, returns 0 or 1. + /// Result type is always `u1`. + /// Uses the `un_op` field. + bool_to_int, /// Stores a value onto the stack and returns a pointer to it. /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting /// alloca instruction and storing to the alloca. @@ -490,6 +494,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .slice_len, => return Type.initTag(.usize), + .bool_to_int => return Type.initTag(.u1), + .call => { const callee_ty = air.typeOf(datas[inst].pl_op.operand); return callee_ty.fnReturnType(); diff --git a/src/AstGen.zig b/src/AstGen.zig index 34f906fab1..b5e5d60b2c 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7754,6 +7754,7 @@ pub const simple_types = std.ComptimeStringMap(Zir.Inst.Ref, .{ .{ "u32", .u32_type }, .{ "u64", .u64_type }, .{ "u128", .u128_type }, + .{ "u1", .u1_type }, .{ "u8", .u8_type }, .{ "undefined", .undef }, .{ "usize", .usize_type }, @@ -8400,6 +8401,7 @@ fn rvalue( const as_usize = @as(u64, @enumToInt(Zir.Inst.Ref.usize_type)) << 32; const as_void = @as(u64, @enumToInt(Zir.Inst.Ref.void_type)) << 32; switch ((@as(u64, @enumToInt(ty_inst)) << 32) | @as(u64, @enumToInt(result))) { + as_ty | @enumToInt(Zir.Inst.Ref.u1_type), as_ty | @enumToInt(Zir.Inst.Ref.u8_type), as_ty | @enumToInt(Zir.Inst.Ref.i8_type), as_ty | @enumToInt(Zir.Inst.Ref.u16_type), diff --git a/src/Liveness.zig b/src/Liveness.zig index a4c6d8c016..a44b582424 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -291,6 +291,7 @@ fn analyzeInst( .is_err_ptr, .is_non_err_ptr, .ptrtoint, + .bool_to_int, .ret, => { const operand = inst_datas[inst].un_op; diff --git a/src/Sema.zig b/src/Sema.zig index 46a41426c8..88a83fd661 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5848,8 +5848,14 @@ fn zirAlignOf(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirBoolToInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirBoolToInt", .{}); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand = sema.resolveInst(inst_data.operand); + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(Type.initTag(.u1)); + const bool_ints = [2]Air.Inst.Ref{ .zero, .one }; + return bool_ints[@boolToInt(val.toBool())]; + } + return block.addUnOp(.bool_to_int, operand); } fn zirEmbedFile(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8252,6 +8258,7 @@ fn typeHasOnePossibleValue( .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, diff --git a/src/Zir.zig b/src/Zir.zig index 2b88a3415d..a8320b65d4 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -1633,6 +1633,7 @@ pub const Inst = struct { /// value and may instead be used as a sentinel to indicate null. none, + u1_type, u8_type, i8_type, u16_type, @@ -1719,6 +1720,10 @@ pub const Inst = struct { pub const typed_value_map = std.enums.directEnumArray(Ref, TypedValue, 0, .{ .none = undefined, + .u1_type = .{ + .ty = Type.initTag(.type), + .val = Value.initTag(.u1_type), + }, .u8_type = .{ .ty = Type.initTag(.type), .val = Value.initTag(.u8_type), diff --git a/src/codegen.zig b/src/codegen.zig index 4924b68ca3..3b822c0f88 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dbg_stmt => try self.airDbgStmt(inst), .floatcast => try self.airFloatCast(inst), .intcast => try self.airIntCast(inst), + .bool_to_int => try self.airBoolToInt(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), .is_null => try self.airIsNull(inst), @@ -1110,6 +1111,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else operand; + return self.finishAir(inst, result, .{ un_op, .none, .none }); + } + fn airNot(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index fa254af293..7299b21a61 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -925,6 +925,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .call => try airCall(o, inst), .dbg_stmt => try airDbgStmt(o, inst), .intcast => try airIntCast(o, inst), + .bool_to_int => try airBoolToInt(o, inst), .load => try airLoad(o, inst), .ret => try airRet(o, inst), .store => try airStore(o, inst), @@ -1083,6 +1084,20 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { return local; } +fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + const un_op = o.air.instructions.items(.data)[inst].un_op; + const writer = o.writer(); + const inst_ty = o.air.typeOfIndex(inst); + const operand = try o.resolveInst(un_op); + const local = try o.allocLocal(inst_ty, .Const); + try writer.writeAll(" = "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); + return local; +} + fn airStore(o: *Object, inst: Air.Inst.Index) !CValue { // *a = b; const bin_op = o.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 74a51e6634..22f117aa1c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -961,6 +961,7 @@ pub const FuncGen = struct { .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), .bitcast => try self.airBitCast(inst), + .bool_to_int=> try self.airBoolToInt(inst), .block => try self.airBlock(inst), .br => try self.airBr(inst), .switch_br => try self.airSwitchBr(inst), @@ -1656,6 +1657,15 @@ pub const FuncGen = struct { return self.builder.buildBitCast(operand, dest_type, ""); } + fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const un_op = self.air.instructions.items(.data)[inst].un_op; + const operand = try self.resolveInst(un_op); + return operand; + } + fn airArg(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { const arg_val = self.args[self.arg_index]; self.arg_index += 1; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 0c9e513742..4f5df73f8d 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -885,7 +885,7 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // Both stage1 and stage2 LLVM backend put the object file in the cache directory. if (self.base.options.use_llvm) { // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1) try self.flushModule(comp); + if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, @@ -1269,7 +1269,10 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { // TODO: remove when stage2 can build compiler_rt.zig, c.zig and ssp.zig // compiler-rt, libc and libssp - if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and build_options.is_stage1) { + if (is_exe_or_dyn_lib and + !self.base.options.skip_linker_dependencies and + build_options.is_stage1 and self.base.options.use_stage1) + { if (!self.base.options.link_libc) { try argv.append(comp.libc_static_lib.?.full_object_path); } diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 502575f3c8..9ddebd3453 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1257,7 +1257,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { // Both stage1 and stage2 LLVM backend put the object file in the cache directory. if (self.base.options.use_llvm) { // Stage2 has to call flushModule since that outputs the LLVM object file. - if (!build_options.is_stage1) try self.flushModule(comp); + if (!build_options.is_stage1 or !self.base.options.use_stage1) try self.flushModule(comp); const obj_basename = try std.zig.binNameAlloc(arena, .{ .root_name = self.base.options.root_name, @@ -1287,7 +1287,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { const allow_shlib_undefined = self.base.options.allow_shlib_undefined orelse !self.base.options.is_native_os; const compiler_rt_path: ?[]const u8 = if (self.base.options.include_compiler_rt) blk: { // TODO: remove when stage2 can build compiler_rt.zig - if (!build_options.is_stage1) break :blk null; + if (!build_options.is_stage1 or !self.base.options.use_stage1) break :blk null; // In the case of build-obj we include the compiler-rt symbols directly alongside // the symbols of the root source file, in the same compilation unit. @@ -1605,7 +1605,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { if (is_exe_or_dyn_lib and !self.base.options.skip_linker_dependencies and !self.base.options.link_libc and - build_options.is_stage1) + build_options.is_stage1 and + self.base.options.use_stage1) { try argv.append(comp.libc_static_lib.?.full_object_path); } diff --git a/src/print_air.zig b/src/print_air.zig index c20a6995e5..2d5d6b588e 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -137,6 +137,7 @@ const Writer = struct { .is_err_ptr, .is_non_err_ptr, .ptrtoint, + .bool_to_int, .ret, => try w.writeUnOp(s, inst), diff --git a/src/type.zig b/src/type.zig index 4dd1a15fdd..d828df550b 100644 --- a/src/type.zig +++ b/src/type.zig @@ -23,6 +23,7 @@ pub const Type = extern union { pub fn zigTypeTag(self: Type) std.builtin.TypeId { switch (self.tag()) { + .u1, .u8, .i8, .u16, @@ -638,6 +639,7 @@ pub const Type = extern union { if (self.tag_if_small_enough < Tag.no_payload_count) { return Type{ .tag_if_small_enough = self.tag_if_small_enough }; } else switch (self.ptr_otherwise.tag) { + .u1, .u8, .i8, .u16, @@ -819,6 +821,7 @@ pub const Type = extern union { while (true) { const t = ty.tag(); switch (t) { + .u1, .u8, .i8, .u16, @@ -1082,6 +1085,7 @@ pub const Type = extern union { pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value { switch (self.tag()) { + .u1 => return Value.initTag(.u1_type), .u8 => return Value.initTag(.u8_type), .i8 => return Value.initTag(.i8_type), .u16 => return Value.initTag(.u16_type), @@ -1141,6 +1145,7 @@ pub const Type = extern union { pub fn hasCodeGenBits(self: Type) bool { return switch (self.tag()) { + .u1, .u8, .i8, .u16, @@ -1321,6 +1326,7 @@ pub const Type = extern union { /// Asserts that hasCodeGenBits() is true. pub fn abiAlignment(self: Type, target: Target) u32 { return switch (self.tag()) { + .u1, .u8, .i8, .bool, @@ -1539,6 +1545,7 @@ pub const Type = extern union { @panic("TODO abiSize unions"); }, + .u1, .u8, .i8, .bool, @@ -1704,7 +1711,7 @@ pub const Type = extern union { .u8, .i8 => 8, - .bool => 1, + .bool, .u1 => 1, .vector => { const payload = self.castTag(.vector).?.data; @@ -2217,12 +2224,13 @@ pub const Type = extern union { pub fn isUnsignedInt(self: Type) bool { return switch (self.tag()) { .int_unsigned, - .u8, .usize, .c_ushort, .c_uint, .c_ulong, .c_ulonglong, + .u1, + .u8, .u16, .u32, .u64, @@ -2244,6 +2252,7 @@ pub const Type = extern union { .signedness = .signed, .bits = self.castTag(.int_signed).?.data, }, + .u1 => .{ .signedness = .unsigned, .bits = 1 }, .u8 => .{ .signedness = .unsigned, .bits = 8 }, .i8 => .{ .signedness = .signed, .bits = 8 }, .u16 => .{ .signedness = .unsigned, .bits = 16 }, @@ -2406,6 +2415,7 @@ pub const Type = extern union { .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, @@ -2446,6 +2456,7 @@ pub const Type = extern union { .c_longdouble, .comptime_int, .comptime_float, + .u1, .u8, .i8, .u16, @@ -2911,6 +2922,7 @@ pub const Type = extern union { /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. pub const Tag = enum { // The first section of this enum are tags that require no payload. + u1, u8, i8, u16, @@ -3018,6 +3030,7 @@ pub const Type = extern union { pub fn Type(comptime t: Tag) type { return switch (t) { + .u1, .u8, .i8, .u16, diff --git a/src/value.zig b/src/value.zig index fc34473921..5d9fd27414 100644 --- a/src/value.zig +++ b/src/value.zig @@ -22,6 +22,7 @@ pub const Value = extern union { pub const Tag = enum { // The first section of this enum are tags that require no payload. + u1_type, u8_type, i8_type, u16_type, @@ -138,6 +139,7 @@ pub const Value = extern union { pub fn Type(comptime t: Tag) type { return switch (t) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -314,6 +316,7 @@ pub const Value = extern union { if (self.tag_if_small_enough < Tag.no_payload_count) { return Value{ .tag_if_small_enough = self.tag_if_small_enough }; } else switch (self.ptr_otherwise.tag) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -520,6 +523,7 @@ pub const Value = extern union { comptime assert(fmt.len == 0); var val = start_val; while (true) switch (val.tag()) { + .u1_type => return out_stream.writeAll("u1"), .u8_type => return out_stream.writeAll("u8"), .i8_type => return out_stream.writeAll("i8"), .u16_type => return out_stream.writeAll("u16"), @@ -671,6 +675,7 @@ pub const Value = extern union { pub fn toType(self: Value, allocator: *Allocator) !Type { return switch (self.tag()) { .ty => self.castTag(.ty).?.data, + .u1_type => Type.initTag(.u1), .u8_type => Type.initTag(.u8), .i8_type => Type.initTag(.i8), .u16_type => Type.initTag(.u16), @@ -1150,6 +1155,7 @@ pub const Value = extern union { var hasher = std.hash.Wyhash.init(0); switch (self.tag()) { + .u1_type, .u8_type, .i8_type, .u16_type, @@ -1502,6 +1508,7 @@ pub const Value = extern union { return switch (self.tag()) { .ty, .int_type, + .u1_type, .u8_type, .i8_type, .u16_type, diff --git a/test/behavior.zig b/test/behavior.zig index 101ee2ce53..a286b1a6e2 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -2,11 +2,9 @@ const builtin = @import("builtin"); test { // Tests that pass for both. - {} + _ = @import("behavior/bool.zig"); - if (builtin.zig_is_stage2) { - // Tests that only pass for stage2. - } else { + if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. _ = @import("behavior/align.zig"); _ = @import("behavior/alignof.zig"); @@ -20,7 +18,6 @@ test { _ = @import("behavior/bit_shifting.zig"); _ = @import("behavior/bitcast.zig"); _ = @import("behavior/bitreverse.zig"); - _ = @import("behavior/bool.zig"); _ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1076.zig"); _ = @import("behavior/bugs/1111.zig"); -- cgit v1.2.3 From a5c6e51f03ab164e64b1a1d8370071dd1e670458 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 15:59:51 -0700 Subject: stage2: more principled approach to comptime references * AIR no longer has a `variables` array. Instead of the `varptr` instruction, Sema emits a constant with a `decl_ref`. * AIR no longer has a `ref` instruction. There is no longer any instruction that takes a value and returns a pointer to it. If this is desired, Sema must either create an anynomous Decl and return a constant `decl_ref`, or in the case of a runtime value, emit an `alloc` instruction, `store` the value to it, and then return the `alloc`. * The `ref_val` Value Tag is eliminated. `decl_ref` should be used instead. Also added is `eu_payload_ptr` which points to the payload of an error union, given an error union pointer. In general, Sema should avoid calling `analyzeRef` if it can be helped. For example in the case of field_val and elem_val, there should never be a reason to create a temporary (alloc or decl). Recent previous commits made progress along that front. There is a new abstraction in Sema, which looks like this: var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); // here 'anon_decl.arena()` may be used const decl = try anon_decl.finish(ty, val); // decl is typically now used with `decl_ref`. This pattern is used to upgrade `ref_val` usages to `decl_ref` usages. Additional improvements: * Sema: fix source location resolution for calling convention expression. * Sema: properly report "unable to resolve comptime value" for loads of global variables. There is now a set of functions which can be called if the callee wants to obtain the Value even if the tag is `variable` (indicating comptime-known address but runtime-known value). * Sema: `coerce` resolves builtin types before checking equality. * Sema: fix `u1_type` missing from `addType`, making this type have a slightly more efficient representation in AIR. * LLVM backend: fix `genTypedValue` for tags `decl_ref` and `variable` to properly do an LLVMConstBitCast. * Remove unused parameter from `Value.toEnum`. After this commit, some test cases are no longer passing. This is due to the more principled approach to comptime references causing more anonymous decls to get sent to the linker for codegen. However, in all these cases the decls are not actually referenced by the runtime machine code. A future commit in this branch will implement garbage collection of decls so that unused decls do not get sent to the linker for codegen. This will make the tests go back to passing. --- src/Air.zig | 14 ---- src/Liveness.zig | 2 - src/Module.zig | 47 ++++++++++- src/Sema.zig | 191 ++++++++++++++++++++++++++++-------------- src/codegen.zig | 41 --------- src/codegen/c.zig | 41 +-------- src/codegen/llvm.zig | 35 +------- src/codegen/llvm/bindings.zig | 3 + src/codegen/wasm.zig | 59 ++++++------- src/link/Wasm.zig | 2 +- src/print_air.zig | 13 +-- src/value.zig | 71 +++++++--------- test/cases.zig | 5 +- 13 files changed, 246 insertions(+), 278 deletions(-) (limited to 'src/codegen.zig') diff --git a/src/Air.zig b/src/Air.zig index bd7b3af733..fb95d60d00 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -15,7 +15,6 @@ instructions: std.MultiArrayList(Inst).Slice, /// The first few indexes are reserved. See `ExtraIndex` for the values. extra: []const u32, values: []const Value, -variables: []const *Module.Var, pub const ExtraIndex = enum(u32) { /// Payload index of the main `Block` in the `extra` array. @@ -193,20 +192,10 @@ pub const Inst = struct { /// Result type is always `u1`. /// Uses the `un_op` field. bool_to_int, - /// Stores a value onto the stack and returns a pointer to it. - /// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting - /// alloca instruction and storing to the alloca. - /// Uses the `ty_op` field. - ref, /// Return a value from a function. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `un_op` field. ret, - /// Returns a pointer to a global variable. - /// Uses the `ty_pl` field. Index is into the `variables` array. - /// TODO this can be modeled simply as a constant with a decl ref and then - /// the variables array can be removed from Air. - varptr, /// Write a value to a pointer. LHS is pointer, RHS is value. /// Result type is always void. /// Uses the `bin_op` field. @@ -454,7 +443,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .assembly, .block, .constant, - .varptr, .struct_field_ptr, .struct_field_val, => return air.getRefType(datas[inst].ty_pl.ty), @@ -462,7 +450,6 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -550,7 +537,6 @@ pub fn deinit(air: *Air, gpa: *std.mem.Allocator) void { air.instructions.deinit(gpa); gpa.free(air.extra); gpa.free(air.values); - gpa.free(air.variables); air.* = undefined; } diff --git a/src/Liveness.zig b/src/Liveness.zig index a44b582424..7ba062fa31 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -256,14 +256,12 @@ fn analyzeInst( .const_ty, .breakpoint, .dbg_stmt, - .varptr, .unreach, => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }), .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, diff --git a/src/Module.zig b/src/Module.zig index 48c2eb8d0d..f89f72bc65 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1324,6 +1324,42 @@ pub const Scope = struct { block.instructions.appendAssumeCapacity(result_index); return result_index; } + + pub fn startAnonDecl(block: *Block) !WipAnonDecl { + return WipAnonDecl{ + .block = block, + .new_decl_arena = std.heap.ArenaAllocator.init(block.sema.gpa), + .finished = false, + }; + } + + pub const WipAnonDecl = struct { + block: *Scope.Block, + new_decl_arena: std.heap.ArenaAllocator, + finished: bool, + + pub fn arena(wad: *WipAnonDecl) *Allocator { + return &wad.new_decl_arena.allocator; + } + + pub fn deinit(wad: *WipAnonDecl) void { + if (!wad.finished) { + wad.new_decl_arena.deinit(); + } + wad.* = undefined; + } + + pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value) !*Decl { + const new_decl = try wad.block.sema.mod.createAnonymousDecl(&wad.block.base, .{ + .ty = ty, + .val = val, + }); + errdefer wad.block.sema.mod.deleteAnonDecl(&wad.block.base, new_decl); + try new_decl.finalizeNewArena(&wad.new_decl_arena); + wad.finished = true; + return new_decl; + } + }; }; }; @@ -1700,6 +1736,7 @@ pub const SrcLoc = struct { .node_offset_fn_type_cc => |node_off| { const tree = try src_loc.file_scope.getTree(gpa); + const node_datas = tree.nodes.items(.data); const node_tags = tree.nodes.items(.tag); const node = src_loc.declRelativeToNodeIndex(node_off); var params: [1]ast.Node.Index = undefined; @@ -1708,6 +1745,13 @@ pub const SrcLoc = struct { .fn_proto_multi => tree.fnProtoMulti(node), .fn_proto_one => tree.fnProtoOne(¶ms, node), .fn_proto => tree.fnProto(node), + .fn_decl => switch (node_tags[node_datas[node].lhs]) { + .fn_proto_simple => tree.fnProtoSimple(¶ms, node_datas[node].lhs), + .fn_proto_multi => tree.fnProtoMulti(node_datas[node].lhs), + .fn_proto_one => tree.fnProtoOne(¶ms, node_datas[node].lhs), + .fn_proto => tree.fnProto(node_datas[node].lhs), + else => unreachable, + }, else => unreachable, }; const main_tokens = tree.nodes.items(.main_token); @@ -2935,7 +2979,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const break_index = try sema.analyzeBody(&block_scope, body); const result_ref = zir_datas[break_index].@"break".operand; const src: LazySrcLoc = .{ .node_offset = 0 }; - const decl_tv = try sema.resolveInstConst(&block_scope, src, result_ref); + const decl_tv = try sema.resolveInstValue(&block_scope, src, result_ref); const align_val = blk: { const align_ref = decl.zirAlignRef(); if (align_ref == .none) break :blk Value.initTag(.null_value); @@ -3603,7 +3647,6 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air { .instructions = sema.air_instructions.toOwnedSlice(), .extra = sema.air_extra.toOwnedSlice(gpa), .values = sema.air_values.toOwnedSlice(gpa), - .variables = sema.air_variables.toOwnedSlice(gpa), }; } diff --git a/src/Sema.zig b/src/Sema.zig index 88a83fd661..95baae7e92 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14,7 +14,6 @@ code: Zir, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: std.ArrayListUnmanaged(u32) = .{}, air_values: std.ArrayListUnmanaged(Value) = .{}, -air_variables: std.ArrayListUnmanaged(*Module.Var) = .{}, /// Maps ZIR to AIR. inst_map: InstMap = .{}, /// When analyzing an inline function call, owner_decl is the Decl of the caller @@ -76,7 +75,6 @@ pub fn deinit(sema: *Sema) void { sema.air_instructions.deinit(gpa); sema.air_extra.deinit(gpa); sema.air_values.deinit(gpa); - sema.air_variables.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); sema.* = undefined; @@ -639,16 +637,40 @@ fn analyzeAsType( return val.toType(sema.arena); } +/// May return Value Tags: `variable`, `undef`. +/// See `resolveConstValue` for an alternative. +fn resolveValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + air_ref: Air.Inst.Ref, +) CompileError!Value { + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + return val; + } + return sema.failWithNeededComptime(block, src); +} + +/// Will not return Value Tags: `variable`, `undef`. Instead they will emit compile errors. +/// See `resolveValue` for an alternative. fn resolveConstValue( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, air_ref: Air.Inst.Ref, ) CompileError!Value { - return (try sema.resolveDefinedValue(block, src, air_ref)) orelse - return sema.failWithNeededComptime(block, src); + if (try sema.resolveMaybeUndefValAllowVariables(block, src, air_ref)) |val| { + switch (val.tag()) { + .undef => return sema.failWithUseOfUndef(block, src), + .variable => return sema.failWithNeededComptime(block, src), + else => return val, + } + } + return sema.failWithNeededComptime(block, src); } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return a compile error. fn resolveDefinedValue( sema: *Sema, block: *Scope.Block, @@ -664,11 +686,27 @@ fn resolveDefinedValue( return null; } +/// Value Tag `variable` causes this function to return `null`. +/// Value Tag `undef` causes this function to return the Value. fn resolveMaybeUndefVal( sema: *Sema, block: *Scope.Block, src: LazySrcLoc, inst: Air.Inst.Ref, +) CompileError!?Value { + const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; + if (val.tag() == .variable) { + return sema.failWithNeededComptime(block, src); + } + return val; +} + +/// Returns all Value tags including `variable` and `undef`. +fn resolveMaybeUndefValAllowVariables( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + inst: Air.Inst.Ref, ) CompileError!?Value { // First section of indexes correspond to a set number of constant values. var i: usize = @enumToInt(inst); @@ -734,6 +772,8 @@ fn resolveInt( return val.toUnsignedInt(); } +// Returns a compile error if the value has tag `variable`. See `resolveInstValue` for +// a function that does not. pub fn resolveInstConst( sema: *Sema, block: *Scope.Block, @@ -748,6 +788,22 @@ pub fn resolveInstConst( }; } +// Value Tag may be `undef` or `variable`. +// See `resolveInstConst` for an alternative. +pub fn resolveInstValue( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, +) CompileError!TypedValue { + const air_ref = sema.resolveInst(zir_ref); + const val = try sema.resolveValue(block, src, air_ref); + return TypedValue{ + .ty = sema.typeOf(air_ref), + .val = val, + }; +} + fn zirBitcastResultPtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); @@ -1707,7 +1763,7 @@ fn zirStr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclRef(block, .unneeded, new_decl); + return sema.analyzeDeclRef(new_decl); } fn zirInt(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2090,10 +2146,7 @@ fn zirExport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErro const linkage_index = struct_obj.fields.getIndex("linkage").?; const section_index = struct_obj.fields.getIndex("section").?; const export_name = try fields[name_index].toAllocatedBytes(sema.arena); - const linkage = fields[linkage_index].toEnum( - struct_obj.fields.values()[linkage_index].ty, - std.builtin.GlobalLinkage, - ); + const linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage); if (linkage != .Strong) { return sema.mod.fail(&block.base, src, "TODO: implement exporting with non-strong linkage", .{}); @@ -2194,7 +2247,7 @@ fn zirDeclRef(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const decl_name = inst_data.get(sema.code); const decl = try sema.lookupIdentifier(block, src, decl_name); - return sema.analyzeDeclRef(block, src, decl); + return sema.analyzeDeclRef(decl); } fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -2978,14 +3031,9 @@ fn zirErrUnionPayloadPtr( if (val.getError()) |name| { return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); } - const data = val.castTag(.error_union).?.data; - // The same Value represents the pointer to the error union and the payload. return sema.addConstant( operand_pointer_ty, - try Value.Tag.ref_val.create( - sema.arena, - data, - ), + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), ); } @@ -6296,7 +6344,7 @@ fn zirFuncExtended( const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref); - break :blk cc_tv.val.toEnum(cc_tv.ty, std.builtin.CallingConvention); + break :blk cc_tv.val.toEnum(std.builtin.CallingConvention); } else .Unspecified; const align_val: Value = if (small.has_align) blk: { @@ -6554,7 +6602,7 @@ fn safetyPanic( }); errdefer sema.mod.deleteAnonDecl(&block.base, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - break :msg_inst try sema.analyzeDeclRef(block, .unneeded, new_decl); + break :msg_inst try sema.analyzeDeclRef(new_decl); }; const casted_msg_inst = try sema.coerce(block, Type.initTag(.const_slice_u8), msg_inst, src); @@ -6761,11 +6809,16 @@ fn fieldPtr( switch (object_ty.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, object_ty.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + ), ), ); } else { @@ -6780,18 +6833,25 @@ fn fieldPtr( .Pointer => { const ptr_child = object_ty.elemType(); if (ptr_child.isSlice()) { + // Here for the ptr and len fields what we need to do is the situation + // when a temporary has its address taken, e.g. `&a[c..d].len`. + // This value may be known at compile-time or runtime. In the former + // case, it should create an anonymous Decl and return a decl_ref to it. + // In the latter case, it should add an `alloc` instruction, store + // the runtime value to it, and then return the `alloc`. + // In both cases the pointer should be const. if (mem.eql(u8, field_name, "ptr")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to pointer field of slice '{}'", + "TODO: implement reference to 'ptr' field of slice '{}'", .{object_ty}, ); } else if (mem.eql(u8, field_name, "len")) { return mod.fail( &block.base, field_name_src, - "cannot obtain reference to length field of slice '{}'", + "TODO: implement reference to 'len' field of slice '{}'", .{object_ty}, ); } else { @@ -6805,11 +6865,16 @@ fn fieldPtr( } else switch (ptr_child.zigTypeTag()) { .Array => { if (mem.eql(u8, field_name, "len")) { + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.int_u64.create(arena, ptr_child.arrayLen()), + try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + ), ), ); } else { @@ -6848,13 +6913,16 @@ fn fieldPtr( }); } else (try mod.getErrorValue(field_name)).key; + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create( + try Value.Tag.decl_ref.create( arena, - try Value.Tag.@"error".create(arena, .{ - .name = name, - }), + try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + ), ), ); }, @@ -6901,10 +6969,17 @@ fn fieldPtr( return mod.failWithOwnedErrorMsg(&block.base, msg); }; const field_index_u32 = @intCast(u32, field_index); - const enum_val = try Value.Tag.enum_field_index.create(arena, field_index_u32); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); return sema.addConstant( try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.ref_val.create(arena, enum_val), + try Value.Tag.decl_ref.create( + arena, + try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + ), + ), ); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), @@ -6951,7 +7026,7 @@ fn namespaceLookupRef( decl_name: []const u8, ) CompileError!?Air.Inst.Ref { const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null; - return try sema.analyzeDeclRef(block, src, decl); + return try sema.analyzeDeclRef(decl); } fn structFieldPtr( @@ -7207,13 +7282,15 @@ fn elemPtrArray( fn coerce( sema: *Sema, block: *Scope.Block, - dest_type: Type, + dest_type_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (dest_type.tag() == .var_args_param) { + if (dest_type_unresolved.tag() == .var_args_param) { return sema.coerceVarArgParam(block, inst, inst_src); } + const dest_type_src = inst_src; // TODO better source location + const dest_type = try sema.resolveTypeFields(block, dest_type_src, dest_type_unresolved); const inst_ty = sema.typeOf(inst); // If the types are the same, we can return the operand. @@ -7554,17 +7631,17 @@ fn analyzeDeclVal( if (sema.decl_val_table.get(decl)) |result| { return result; } - const decl_ref = try sema.analyzeDeclRef(block, src, decl); + const decl_ref = try sema.analyzeDeclRef(decl); const result = try sema.analyzeLoad(block, src, decl_ref, src); if (Air.refToIndex(result)) |index| { if (sema.air_instructions.items(.tag)[index] == .constant) { - sema.decl_val_table.put(sema.gpa, decl, result) catch {}; + try sema.decl_val_table.put(sema.gpa, decl, result); } } return result; } -fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) CompileError!Air.Inst.Ref { +fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.func) |func| { @@ -7576,8 +7653,10 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl }; const decl_tv = try decl.typedValue(); - if (decl_tv.val.tag() == .variable) { - return sema.analyzeVarRef(block, src, decl_tv); + if (decl_tv.val.castTag(.variable)) |payload| { + const variable = payload.data; + const ty = try Module.simplePtrType(sema.arena, decl_tv.ty, variable.is_mutable, .One); + return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); } return sema.addConstant( try Module.simplePtrType(sema.arena, decl_tv.ty, false, .One), @@ -7585,26 +7664,6 @@ fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl ); } -fn analyzeVarRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, tv: TypedValue) CompileError!Air.Inst.Ref { - const variable = tv.val.castTag(.variable).?.data; - - const ty = try Module.simplePtrType(sema.arena, tv.ty, variable.is_mutable, .One); - if (!variable.is_mutable and !variable.is_extern) { - return sema.addConstant(ty, try Value.Tag.ref_val.create(sema.arena, variable.init)); - } - - const gpa = sema.gpa; - try sema.requireRuntimeBlock(block, src); - try sema.air_variables.append(gpa, variable); - return block.addInst(.{ - .tag = .varptr, - .data = .{ .ty_pl = .{ - .ty = try sema.addType(ty), - .payload = @intCast(u32, sema.air_variables.items.len - 1), - } }, - }); -} - fn analyzeRef( sema: *Sema, block: *Scope.Block, @@ -7615,11 +7674,21 @@ fn analyzeRef( const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { - return sema.addConstant(ptr_type, try Value.Tag.ref_val.create(sema.arena, val)); + var anon_decl = try block.startAnonDecl(); + defer anon_decl.deinit(); + return sema.addConstant( + ptr_type, + try Value.Tag.decl_ref.create( + sema.arena, + try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), + ), + ); } try sema.requireRuntimeBlock(block, src); - return block.addTyOp(.ref, ptr_type, operand); + const alloc = try block.addTy(.alloc, ptr_type); + try sema.storePtr(block, src, alloc, operand); + return alloc; } fn analyzeLoad( @@ -8447,12 +8516,12 @@ fn getTmpAir(sema: Sema) Air { .instructions = sema.air_instructions.slice(), .extra = sema.air_extra.items, .values = sema.air_values.items, - .variables = sema.air_variables.items, }; } pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { switch (ty.tag()) { + .u1 => return .u1_type, .u8 => return .u8_type, .i8 => return .i8_type, .u16 => return .u16_type, diff --git a/src/codegen.zig b/src/codegen.zig index 3b822c0f88..b7ba367d54 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -848,13 +848,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .loop => try self.airLoop(inst), .not => try self.airNot(inst), .ptrtoint => try self.airPtrToInt(inst), - .ref => try self.airRef(inst), .ret => try self.airRet(inst), .store => try self.airStore(inst), .struct_field_ptr=> try self.airStructFieldPtr(inst), .struct_field_val=> try self.airStructFieldVal(inst), .switch_br => try self.airSwitch(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSlicePtr(inst), .slice_len => try self.airSliceLen(inst), @@ -1340,13 +1338,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } - fn airVarPtr(self: *Self, inst: Air.Inst.Index) !void { - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { - else => return self.fail("TODO implement varptr for {}", .{self.target.cpu.arch}), - }; - return self.finishAir(inst, result, .{ .none, .none, .none }); - } - fn airSlicePtr(self: *Self, inst: Air.Inst.Index) !void { const ty_op = self.air.instructions.items(.data)[inst].ty_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { @@ -2833,38 +2824,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return bt.finishAir(result); } - fn airRef(self: *Self, inst: Air.Inst.Index) !void { - const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { - const operand_ty = self.air.typeOf(ty_op.operand); - const operand = try self.resolveInst(ty_op.operand); - switch (operand) { - .unreach => unreachable, - .dead => unreachable, - .none => break :result MCValue{ .none = {} }, - - .immediate, - .register, - .ptr_stack_offset, - .ptr_embedded_in_code, - .compare_flags_unsigned, - .compare_flags_signed, - => { - const stack_offset = try self.allocMemPtr(inst); - try self.genSetStack(operand_ty, stack_offset, operand); - break :result MCValue{ .ptr_stack_offset = stack_offset }; - }, - - .stack_offset => |offset| break :result MCValue{ .ptr_stack_offset = offset }, - .embedded_in_code => |offset| break :result MCValue{ .ptr_embedded_in_code = offset }, - .memory => |vaddr| break :result MCValue{ .immediate = vaddr }, - - .undef => return self.fail("TODO implement ref on an undefined value", .{}), - } - }; - return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); - } - fn ret(self: *Self, mcv: MCValue) !void { const ret_ty = self.fn_type.fnReturnType(); try self.setRegOrMem(ret_ty, self.ret_mcv, mcv); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7299b21a61..a8ec677753 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -283,22 +283,7 @@ pub const DeclGen = struct { }, else => switch (t.ptrSize()) { .Slice => unreachable, - .Many => { - if (val.castTag(.ref_val)) |ref_val_payload| { - const sub_val = ref_val_payload.data; - if (sub_val.castTag(.bytes)) |bytes_payload| { - const bytes = bytes_payload.data; - try writer.writeByte('('); - try dg.renderType(writer, t); - // TODO: make our own C string escape instead of using std.zig.fmtEscapes - try writer.print(")\"{}\"", .{std.zig.fmtEscapes(bytes)}); - } else { - unreachable; - } - } else { - unreachable; - } - }, + .Many => unreachable, .One => { var arena = std.heap.ArenaAllocator.init(dg.module.gpa); defer arena.deinit(); @@ -934,10 +919,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .br => try airBr(o, inst), .switch_br => try airSwitchBr(o, inst), .wrap_optional => try airWrapOptional(o, inst), - .ref => try airRef(o, inst), .struct_field_ptr => try airStructFieldPtr(o, inst), .struct_field_val => try airStructFieldVal(o, inst), - .varptr => try airVarPtr(o, inst), .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), @@ -996,12 +979,6 @@ fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue return local; } -fn airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue { - const ty_pl = o.air.instructions.items(.data)[inst].ty_pl; - const variable = o.air.variables[ty_pl.payload]; - return CValue{ .decl_ref = variable.owner_decl }; -} - fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue { const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1653,22 +1630,6 @@ fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue { return local; } -fn airRef(o: *Object, inst: Air.Inst.Index) !CValue { - if (o.liveness.isUnused(inst)) - return CValue.none; - - const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const writer = o.writer(); - const operand = try o.resolveInst(ty_op.operand); - - const inst_ty = o.air.typeOfIndex(inst); - const local = try o.allocLocal(inst_ty, .Const); - try writer.writeAll(" = "); - try o.writeCValue(writer, operand); - try writer.writeAll(";\n"); - return local; -} - fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 22f117aa1c..0e9a572bea 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -699,29 +699,12 @@ pub const DeclGen = struct { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; const val = try self.resolveGlobalDecl(decl); - - const usize_type = try self.llvmType(Type.initTag(.usize)); - - // TODO: second index should be the index into the memory! - var indices: [2]*const llvm.Value = .{ - usize_type.constNull(), - usize_type.constNull(), - }; - - return val.constInBoundsGEP(&indices, indices.len); - }, - .ref_val => { - //const elem_value = tv.val.castTag(.ref_val).?.data; - //const elem_type = tv.ty.castPointer().?.data; - //const alloca = fg.?.buildAlloca(try self.llvmType(elem_type)); - //_ = fg.?.builder.buildStore(try self.genTypedValue(.{ .ty = elem_type, .val = elem_value }, fg), alloca); - //return alloca; - // TODO eliminate the ref_val Value Tag - return self.todo("implement const of pointer tag ref_val", .{}); + return val.constBitCast(llvm_type); }, .variable => { const variable = tv.val.castTag(.variable).?.data; - return self.resolveGlobalDecl(variable.owner_decl); + const val = try self.resolveGlobalDecl(variable.owner_decl); + return val.constBitCast(llvm_type); }, .slice => { const slice = tv.val.castTag(.slice).?.data; @@ -977,7 +960,6 @@ pub const FuncGen = struct { .ret => try self.airRet(inst), .store => try self.airStore(inst), .assembly => try self.airAssembly(inst), - .varptr => try self.airVarPtr(inst), .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), @@ -1001,7 +983,6 @@ pub const FuncGen = struct { .constant => unreachable, .const_ty => unreachable, - .ref => unreachable, // TODO eradicate this instruction .unreach => self.airUnreach(inst), .dbg_stmt => blk: { // TODO: implement debug info @@ -1180,16 +1161,6 @@ pub const FuncGen = struct { return null; } - fn airVarPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) - return null; - - const ty_pl = self.air.instructions.items(.data)[inst].ty_pl; - const variable = self.air.variables[ty_pl.payload]; - const decl_llvm_value = self.dg.resolveGlobalDecl(variable.owner_decl); - return decl_llvm_value; - } - fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 6d09d69447..0977d6128d 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -112,6 +112,9 @@ pub const Value = opaque { ConstantIndices: [*]const *const Value, NumIndices: c_uint, ) *const Value; + + pub const constBitCast = LLVMConstBitCast; + extern fn LLVMConstBitCast(ConstantVal: *const Value, ToType: *const Type) *const Value; }; pub const Type = opaque { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index bf9010fbff..37cc6bc59c 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -754,22 +754,21 @@ pub const Context = struct { } /// Generates the wasm bytecode for the declaration belonging to `Context` - pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result { - switch (typed_value.ty.zigTypeTag()) { + pub fn gen(self: *Context, ty: Type, val: Value) InnerError!Result { + switch (ty.zigTypeTag()) { .Fn => { try self.genFunctype(); - if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions + if (val.tag() == .extern_fn) { + return Result.appended; // don't need code body for extern functions + } return self.fail("TODO implement wasm codegen for function pointers", .{}); }, .Array => { - if (typed_value.val.castTag(.bytes)) |payload| { - if (typed_value.ty.sentinel()) |sentinel| { + if (val.castTag(.bytes)) |payload| { + if (ty.sentinel()) |sentinel| { try self.code.appendSlice(payload.data); - switch (try self.gen(.{ - .ty = typed_value.ty.elemType(), - .val = sentinel, - })) { + switch (try self.gen(ty.elemType(), sentinel)) { .appended => return Result.appended, .externally_managed => |data| { try self.code.appendSlice(data); @@ -781,13 +780,17 @@ pub const Context = struct { } else return self.fail("TODO implement gen for more kinds of arrays", .{}); }, .Int => { - const info = typed_value.ty.intInfo(self.target); + const info = ty.intInfo(self.target); if (info.bits == 8 and info.signedness == .unsigned) { - const int_byte = typed_value.val.toUnsignedInt(); + const int_byte = val.toUnsignedInt(); try self.code.append(@intCast(u8, int_byte)); return Result.appended; } - return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty}); + return self.fail("TODO: Implement codegen for int type: '{}'", .{ty}); + }, + .Enum => { + try self.emitConstant(val, ty); + return Result.appended; }, else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}), } @@ -969,7 +972,7 @@ pub const Context = struct { return WValue{ .code_offset = offset }; } - fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void { + fn emitConstant(self: *Context, val: Value, ty: Type) InnerError!void { const writer = self.code.writer(); switch (ty.zigTypeTag()) { .Int => { @@ -982,10 +985,10 @@ pub const Context = struct { const int_info = ty.intInfo(self.target); // write constant switch (int_info.signedness) { - .signed => try leb.writeILEB128(writer, value.toSignedInt()), + .signed => try leb.writeILEB128(writer, val.toSignedInt()), .unsigned => switch (int_info.bits) { - 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, value.toUnsignedInt()))), - 33...64 => try leb.writeILEB128(writer, @bitCast(i64, value.toUnsignedInt())), + 0...32 => try leb.writeILEB128(writer, @bitCast(i32, @intCast(u32, val.toUnsignedInt()))), + 33...64 => try leb.writeILEB128(writer, @bitCast(i64, val.toUnsignedInt())), else => |bits| return self.fail("Wasm TODO: emitConstant for integer with {d} bits", .{bits}), }, } @@ -994,7 +997,7 @@ pub const Context = struct { // write opcode try writer.writeByte(wasm.opcode(.i32_const)); // write constant - try leb.writeILEB128(writer, value.toSignedInt()); + try leb.writeILEB128(writer, val.toSignedInt()); }, .Float => { // write opcode @@ -1005,13 +1008,13 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(opcode)); // write constant switch (ty.floatBits(self.target)) { - 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))), - 64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))), + 0...32 => try writer.writeIntLittle(u32, @bitCast(u32, val.toFloat(f32))), + 64 => try writer.writeIntLittle(u64, @bitCast(u64, val.toFloat(f64))), else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}), } }, .Pointer => { - if (value.castTag(.decl_ref)) |payload| { + if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; // offset into the offset table within the 'data' section @@ -1024,11 +1027,11 @@ pub const Context = struct { try writer.writeByte(wasm.opcode(.i32_load)); try leb.writeULEB128(writer, @as(u32, 0)); try leb.writeULEB128(writer, @as(u32, 0)); - } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()}); + } else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{val.tag()}); }, .Void => {}, .Enum => { - if (value.castTag(.enum_field_index)) |field_index| { + if (val.castTag(.enum_field_index)) |field_index| { switch (ty.tag()) { .enum_simple => { try writer.writeByte(wasm.opcode(.i32_const)); @@ -1049,20 +1052,20 @@ pub const Context = struct { } else { var int_tag_buffer: Type.Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&int_tag_buffer); - try self.emitConstant(value, int_tag_ty); + try self.emitConstant(val, int_tag_ty); } }, .ErrorSet => { - const error_index = self.global_error_set.get(value.getError().?).?; + const error_index = self.global_error_set.get(val.getError().?).?; try writer.writeByte(wasm.opcode(.i32_const)); try leb.writeULEB128(writer, error_index); }, .ErrorUnion => { - const data = value.castTag(.error_union).?.data; + const data = val.castTag(.error_union).?.data; const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); - if (value.getError()) |_| { - // write the error value + if (val.getError()) |_| { + // write the error val try self.emitConstant(data, error_type); // no payload, so write a '0' const @@ -1085,7 +1088,7 @@ pub const Context = struct { } /// Returns a `Value` as a signed 32 bit value. - /// It's illegale to provide a value with a type that cannot be represented + /// It's illegal to provide a value with a type that cannot be represented /// as an integer value. fn valueAsI32(self: Context, val: Value, ty: Type) i32 { switch (ty.zigTypeTag()) { diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 23d0543494..3c3cd4eef3 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -275,7 +275,7 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void { defer context.deinit(); // generate the 'code' section for the function declaration - const result = context.gen(.{ .ty = decl.ty, .val = decl.val }) catch |err| switch (err) { + const result = context.gen(decl.ty, decl.val) catch |err| switch (err) { error.CodegenFail => { decl.analysis = .codegen_failure; try module.failed_decls.put(module.gpa, decl, context.err_msg); diff --git a/src/print_air.zig b/src/print_air.zig index 2d5d6b588e..5d77c303bb 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -15,12 +15,11 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { (@sizeOf(Air.Inst.Tag) + 8); const extra_bytes = air.extra.len * @sizeOf(u32); const values_bytes = air.values.len * @sizeOf(Value); - const variables_bytes = air.variables.len * @sizeOf(*Module.Var); const tomb_bytes = liveness.tomb_bits.len * @sizeOf(usize); const liveness_extra_bytes = liveness.extra.len * @sizeOf(u32); const liveness_special_bytes = liveness.special.count() * 8; const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes + - values_bytes * variables_bytes + @sizeOf(Liveness) + liveness_extra_bytes + + values_bytes + @sizeOf(Liveness) + liveness_extra_bytes + liveness_special_bytes + tomb_bytes; // zig fmt: off @@ -29,7 +28,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { \\# AIR Instructions: {d} ({}) \\# AIR Extra Data: {d} ({}) \\# AIR Values Bytes: {d} ({}) - \\# AIR Variables Bytes: {d} ({}) \\# Liveness tomb_bits: {} \\# Liveness Extra Data: {d} ({}) \\# Liveness special table: {d} ({}) @@ -39,7 +37,6 @@ pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void { air.instructions.len, fmtIntSizeBin(instruction_bytes), air.extra.len, fmtIntSizeBin(extra_bytes), air.values.len, fmtIntSizeBin(values_bytes), - air.variables.len, fmtIntSizeBin(variables_bytes), fmtIntSizeBin(tomb_bytes), liveness.extra.len, fmtIntSizeBin(liveness_extra_bytes), liveness.special.count(), fmtIntSizeBin(liveness_special_bytes), @@ -152,7 +149,6 @@ const Writer = struct { .not, .bitcast, .load, - .ref, .floatcast, .intcast, .optional_payload, @@ -174,7 +170,6 @@ const Writer = struct { .struct_field_ptr => try w.writeStructField(s, inst), .struct_field_val => try w.writeStructField(s, inst), - .varptr => try w.writeVarPtr(s, inst), .constant => try w.writeConstant(s, inst), .assembly => try w.writeAssembly(s, inst), .dbg_stmt => try w.writeDbgStmt(s, inst), @@ -243,12 +238,6 @@ const Writer = struct { try s.print(", {d}", .{extra.data.field_index}); } - fn writeVarPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { - _ = w; - _ = inst; - try s.writeAll("TODO"); - } - fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { const ty_pl = w.air.instructions.items(.data)[inst].ty_pl; const val = w.air.values[ty_pl.payload]; diff --git a/src/value.zig b/src/value.zig index 5d9fd27414..d3317ef31d 100644 --- a/src/value.zig +++ b/src/value.zig @@ -100,8 +100,6 @@ pub const Value = extern union { function, extern_fn, variable, - /// Represents a pointer to another immutable value. - ref_val, /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. @@ -126,6 +124,8 @@ pub const Value = extern union { enum_field_index, @"error", error_union, + /// A pointer to the payload of an error union, based on a pointer to an error union. + eu_payload_ptr, /// An instance of a struct. @"struct", /// An instance of a union. @@ -214,9 +214,9 @@ pub const Value = extern union { .decl_ref, => Payload.Decl, - .ref_val, .repeated, .error_union, + .eu_payload_ptr, => Payload.SubValue, .bytes, @@ -407,15 +407,6 @@ pub const Value = extern union { .function => return self.copyPayloadShallow(allocator, Payload.Function), .extern_fn => return self.copyPayloadShallow(allocator, Payload.Decl), .variable => return self.copyPayloadShallow(allocator, Payload.Variable), - .ref_val => { - const payload = self.castTag(.ref_val).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .comptime_alloc => return self.copyPayloadShallow(allocator, Payload.ComptimeAlloc), .decl_ref => return self.copyPayloadShallow(allocator, Payload.Decl), .elem_ptr => { @@ -443,8 +434,8 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated => { - const payload = self.castTag(.repeated).?; + .repeated, .error_union, .eu_payload_ptr => { + const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ .base = payload.base, @@ -489,15 +480,6 @@ pub const Value = extern union { }, .enum_field_index => return self.copyPayloadShallow(allocator, Payload.U32), .@"error" => return self.copyPayloadShallow(allocator, Payload.Error), - .error_union => { - const payload = self.castTag(.error_union).?; - const new_payload = try allocator.create(Payload.SubValue); - new_payload.* = .{ - .base = payload.base, - .data = try payload.data.copy(allocator), - }; - return Value{ .ptr_otherwise = &new_payload.base }; - }, .@"struct" => @panic("TODO can't copy struct value without knowing the type"), .@"union" => @panic("TODO can't copy union value without knowing the type"), @@ -609,11 +591,6 @@ pub const Value = extern union { .function => return out_stream.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}), .extern_fn => return out_stream.writeAll("(extern function)"), .variable => return out_stream.writeAll("(variable)"), - .ref_val => { - const ref_val = val.castTag(.ref_val).?.data; - try out_stream.writeAll("&const "); - val = ref_val; - }, .comptime_alloc => { const ref_val = val.castTag(.comptime_alloc).?.data.val; try out_stream.writeAll("&"); @@ -648,6 +625,10 @@ pub const Value = extern union { // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}), .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), + .eu_payload_ptr => { + try out_stream.writeAll("(eu_payload_ptr)"); + val = val.castTag(.eu_payload_ptr).?.data; + }, }; } @@ -758,7 +739,6 @@ pub const Value = extern union { .function, .extern_fn, .variable, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -780,18 +760,21 @@ pub const Value = extern union { .@"union", .inferred_alloc, .abi_align_default, + .eu_payload_ptr, => unreachable, }; } /// Asserts the type is an enum type. - pub fn toEnum(val: Value, enum_ty: Type, comptime E: type) E { - _ = enum_ty; - // TODO this needs to resolve other kinds of Value tags rather than - // assuming the tag will be .enum_field_index. - const field_index = val.castTag(.enum_field_index).?.data; - // TODO should `@intToEnum` do this `@intCast` for you? - return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + pub fn toEnum(val: Value, comptime E: type) E { + switch (val.tag()) { + .enum_field_index => { + const field_index = val.castTag(.enum_field_index).?.data; + // TODO should `@intToEnum` do this `@intCast` for you? + return @intToEnum(E, @intCast(@typeInfo(E).Enum.tag_type, field_index)); + }, + else => unreachable, + } } /// Asserts the value is an integer. @@ -1255,6 +1238,9 @@ pub const Value = extern union { .slice => { @panic("TODO Value.hash for slice"); }, + .eu_payload_ptr => { + @panic("TODO Value.hash for eu_payload_ptr"); + }, .int_u64 => { const payload = self.castTag(.int_u64).?; std.hash.autoHash(&hasher, payload.data); @@ -1263,10 +1249,6 @@ pub const Value = extern union { const payload = self.castTag(.int_i64).?; std.hash.autoHash(&hasher, payload.data); }, - .ref_val => { - const payload = self.castTag(.ref_val).?; - std.hash.autoHash(&hasher, payload.data.hash()); - }, .comptime_alloc => { const payload = self.castTag(.comptime_alloc).?; std.hash.autoHash(&hasher, payload.data.val.hash()); @@ -1367,7 +1349,6 @@ pub const Value = extern union { pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { return switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .ref_val => self.castTag(.ref_val).?.data, .decl_ref => self.castTag(.decl_ref).?.data.value(), .elem_ptr => { const elem_ptr = self.castTag(.elem_ptr).?.data; @@ -1379,6 +1360,11 @@ pub const Value = extern union { const container_val = try field_ptr.container_ptr.pointerDeref(allocator); return container_val.fieldValue(allocator, field_ptr.field_index); }, + .eu_payload_ptr => { + const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; + const err_union_val = try err_union_ptr.pointerDeref(allocator); + return err_union_val.castTag(.error_union).?.data; + }, else => unreachable, }; @@ -1390,7 +1376,6 @@ pub const Value = extern union { .bytes => val.castTag(.bytes).?.data.len, .array => val.castTag(.array).?.data.len, .slice => val.castTag(.slice).?.data.len.toUnsignedInt(), - .ref_val => sliceLen(val.castTag(.ref_val).?.data), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; if (decl.ty.zigTypeTag() == .Array) { @@ -1576,7 +1561,6 @@ pub const Value = extern union { .int_i64, .int_big_positive, .int_big_negative, - .ref_val, .comptime_alloc, .decl_ref, .elem_ptr, @@ -1599,6 +1583,7 @@ pub const Value = extern union { .@"union", .null_value, .abi_align_default, + .eu_payload_ptr, => false, .undef => unreachable, diff --git a/test/cases.zig b/test/cases.zig index f235992f71..840ee7a4ac 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -1182,10 +1182,11 @@ pub fn addCases(ctx: *TestContext) !void { var case = ctx.obj("extern variable has no type", linux_x64); case.addError( \\comptime { - \\ _ = foo; + \\ const x = foo + foo; + \\ _ = x; \\} \\extern var foo: i32; - , &[_][]const u8{":2:9: error: unable to resolve comptime value"}); + , &[_][]const u8{":2:15: error: unable to resolve comptime value"}); case.addError( \\export fn entry() void { \\ _ = foo; -- cgit v1.2.3 From 040c6eaaa03bbcfcdeadbe835c1c2f209e9f401e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jul 2021 19:30:37 -0700 Subject: stage2: garbage collect unused anon decls After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now. --- src/Compilation.zig | 10 +++- src/Module.zig | 60 +++++++++++++++++--- src/Sema.zig | 154 ++++++++++++++++++++++++++------------------------- src/codegen.zig | 7 +-- src/codegen/c.zig | 24 +++----- src/codegen/llvm.zig | 51 +++++++++++------ src/codegen/wasm.zig | 1 + src/link/Plan9.zig | 19 +++++-- src/value.zig | 45 +++++++++++---- test/stage2/cbe.zig | 2 +- 10 files changed, 234 insertions(+), 139 deletions(-) (limited to 'src/codegen.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 8672a346c3..f8f8cea328 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2061,11 +2061,19 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .complete, .codegen_failure_retryable => { if (build_options.omit_stage2) @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + const module = self.bin_file.options.module.?; assert(decl.has_tv); assert(decl.ty.hasCodeGenBits()); - try module.linkerUpdateDecl(decl); + if (decl.alive) { + try module.linkerUpdateDecl(decl); + continue; + } + + // Instead of sending this decl to the linker, we actually will delete it + // because we found out that it in fact was never referenced. + module.deleteUnusedDecl(decl); }, }, .codegen_func => |func| switch (func.owner_decl.analysis) { diff --git a/src/Module.zig b/src/Module.zig index f89f72bc65..909e54ffa2 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -255,6 +255,15 @@ pub const Decl = struct { has_align: bool, /// Whether the ZIR code provides a linksection instruction. has_linksection: bool, + /// Flag used by garbage collection to mark and sweep. + /// Decls which correspond to an AST node always have this field set to `true`. + /// Anonymous Decls are initialized with this field set to `false` and then it + /// is the responsibility of machine code backends to mark it `true` whenever + /// a `decl_ref` Value is encountered that points to this Decl. + /// When the `codegen_decl` job is encountered in the main work queue, if the + /// Decl is marked alive, then it sends the Decl to the linker. Otherwise it + /// deletes the Decl on the spot. + alive: bool, /// Represents the position of the code in the output file. /// This is populated regardless of semantic analysis and code generation. @@ -2869,6 +2878,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void { new_decl.val = struct_val; new_decl.has_tv = true; new_decl.owns_tv = true; + new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive. new_decl.analysis = .in_progress; new_decl.generation = mod.generation; @@ -2990,6 +3000,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { if (linksection_ref == .none) break :blk Value.initTag(.null_value); break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; }; + try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty); // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(gpa); @@ -3027,8 +3038,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { const is_inline = decl_tv.ty.fnCallingConvention() == .Inline; if (!is_inline and decl_tv.ty.hasCodeGenBits()) { // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. + // offset table index for it. This allows us to codegen decls out of dependency + // order, increasing how many computations can be done in parallel. try mod.comp.bin_file.allocateDeclIndexes(decl); try mod.comp.work_queue.writeItem(.{ .codegen_func = func }); if (type_changed and mod.emit_h != null) { @@ -3387,6 +3398,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi new_decl.has_align = has_align; new_decl.has_linksection = has_linksection; new_decl.zir_decl_index = @intCast(u32, decl_sub_index); + new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. return; } gpa.free(decl_name); @@ -3526,6 +3538,43 @@ pub fn clearDecl( decl.analysis = .unreferenced; } +pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void { + log.debug("deleteUnusedDecl {*} ({s})", .{ decl, decl.name }); + + // TODO: remove `allocateDeclIndexes` and make the API that the linker backends + // are required to notice the first time `updateDecl` happens and keep track + // of it themselves. However they can rely on getting a `freeDecl` call if any + // `updateDecl` or `updateFunc` calls happen. This will allow us to avoid any call + // into the linker backend here, since the linker backend will never have been told + // about the Decl in the first place. + // Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we + // must call `freeDecl` in the linker backend now. + if (decl.has_tv) { + if (decl.ty.hasCodeGenBits()) { + mod.comp.bin_file.freeDecl(decl); + } + } + + const dependants = decl.dependants.keys(); + assert(dependants[0].namespace.anon_decls.swapRemove(decl)); + + for (dependants) |dep| { + dep.removeDependency(decl); + } + + for (decl.dependencies.keys()) |dep| { + dep.removeDependant(decl); + } + decl.destroy(mod); +} + +pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { + log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name }); + const scope_decl = scope.ownerDecl().?; + assert(scope_decl.namespace.anon_decls.swapRemove(decl)); + decl.destroy(mod); +} + /// Delete all the Export objects that are caused by this Decl. Re-analysis of /// this Decl will cause them to be re-created (or not). fn deleteDeclExports(mod: *Module, decl: *Decl) void { @@ -3713,6 +3762,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node .is_exported = false, .has_linksection = false, .has_align = false, + .alive = false, }; return new_decl; } @@ -3802,12 +3852,6 @@ pub fn analyzeExport( errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); } -pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void { - const scope_decl = scope.ownerDecl().?; - assert(scope_decl.namespace.anon_decls.swapRemove(decl)); - decl.destroy(mod); -} - /// Takes ownership of `name` even if it returns an error. pub fn createAnonymousDeclNamed( mod: *Module, diff --git a/src/Sema.zig b/src/Sema.zig index 95baae7e92..0da38d9a76 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -696,7 +696,7 @@ fn resolveMaybeUndefVal( ) CompileError!?Value { const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; if (val.tag() == .variable) { - return sema.failWithNeededComptime(block, src); + return null; } return val; } @@ -2917,12 +2917,13 @@ fn zirOptionalPayloadPtr( const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.isNull()) { - return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.isNull()) { + return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); + } + // The same Value represents the pointer to the optional and the payload. + return sema.addConstant(child_pointer, pointer_val); } - // The same Value represents the pointer to the optional and the payload. - return sema.addConstant(child_pointer, pointer_val); } try sema.requireRuntimeBlock(block, src); @@ -3027,14 +3028,15 @@ fn zirErrUnionPayloadPtr( const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - if (val.getError()) |name| { - return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + if (val.getError()) |name| { + return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); + } + return sema.addConstant( + operand_pointer_ty, + try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), + ); } - return sema.addConstant( - operand_pointer_ty, - try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), - ); } try sema.requireRuntimeBlock(block, src); @@ -3086,10 +3088,11 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co const result_ty = operand_ty.elemType().errorUnionSet(); if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - const val = try pointer_val.pointerDeref(sema.arena); - assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + if (try pointer_val.pointerDeref(sema.arena)) |val| { + assert(val.getError() != null); + const data = val.castTag(.error_union).?.data; + return sema.addConstant(result_ty, data); + } } try sema.requireRuntimeBlock(block, src); @@ -4920,10 +4923,13 @@ fn analyzeArithmetic( log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); return sema.addConstant(scalar_type, value); + } else { + try sema.requireRuntimeBlock(block, rhs_src); } + } else { + try sema.requireRuntimeBlock(block, lhs_src); } - try sema.requireRuntimeBlock(block, src); const air_tag: Air.Inst.Tag = switch (zir_tag) { .add => .add, .addwrap => .addwrap, @@ -6811,16 +6817,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6867,16 +6867,10 @@ fn fieldPtr( if (mem.eql(u8, field_name, "len")) { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - Type.initTag(.single_const_pointer_to_comptime_int), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - Type.initTag(.comptime_int), - try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + Type.initTag(.comptime_int), + try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()), + )); } else { return mod.fail( &block.base, @@ -6915,16 +6909,10 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + )); }, .Struct, .Opaque, .Union => { if (child_type.getNamespace()) |namespace| { @@ -6971,16 +6959,10 @@ fn fieldPtr( const field_index_u32 = @intCast(u32, field_index); var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - try Module.simplePtrType(arena, child_type, false, .One), - try Value.Tag.decl_ref.create( - arena, - try anon_decl.finish( - child_type, - try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), - ), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + child_type, + try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + )); }, else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), } @@ -7671,21 +7653,18 @@ fn analyzeRef( operand: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const operand_ty = sema.typeOf(operand); - const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { var anon_decl = try block.startAnonDecl(); defer anon_decl.deinit(); - return sema.addConstant( - ptr_type, - try Value.Tag.decl_ref.create( - sema.arena, - try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())), - ), - ); + return sema.analyzeDeclRef(try anon_decl.finish( + operand_ty, + try val.copy(anon_decl.arena()), + )); } try sema.requireRuntimeBlock(block, src); + const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One); const alloc = try block.addTy(.alloc, ptr_type); try sema.storePtr(block, src, alloc, operand); return alloc; @@ -7703,11 +7682,10 @@ fn analyzeLoad( .Pointer => ptr_ty.elemType(), else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr_ty}), }; - if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| blk: { - if (ptr_val.tag() == .int_u64) - break :blk; // do it at runtime - - return sema.addConstant(elem_ty, try ptr_val.pointerDeref(sema.arena)); + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { + if (try ptr_val.pointerDeref(sema.arena)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } } try sema.requireRuntimeBlock(block, src); @@ -8215,6 +8193,36 @@ fn resolvePeerTypes( return sema.typeOf(chosen); } +pub fn resolveTypeLayout( + sema: *Sema, + block: *Scope.Block, + src: LazySrcLoc, + ty: Type, +) CompileError!void { + switch (ty.zigTypeTag()) { + .Pointer => { + return sema.resolveTypeLayout(block, src, ty.elemType()); + }, + .Struct => { + const resolved_ty = try sema.resolveTypeFields(block, src, ty); + const struct_obj = resolved_ty.castTag(.@"struct").?.data; + switch (struct_obj.status) { + .none, .have_field_types => {}, + .field_types_wip, .layout_wip => { + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); + }, + .have_layout => return, + } + struct_obj.status = .layout_wip; + for (struct_obj.fields.values()) |field| { + try sema.resolveTypeLayout(block, src, field.ty); + } + struct_obj.status = .have_layout; + }, + else => {}, + } +} + fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { switch (ty.tag()) { .@"struct" => { @@ -8222,9 +8230,7 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type switch (struct_obj.status) { .none => {}, .field_types_wip => { - return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ - ty, - }); + return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty}); }, .have_field_types, .have_layout, .layout_wip => return ty, } diff --git a/src/codegen.zig b/src/codegen.zig index b7ba367d54..d16a87adca 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -184,6 +184,7 @@ pub fn generateSymbol( if (typed_value.val.castTag(.decl_ref)) |payload| { const decl = payload.data; if (decl.analysis != .complete) return error.AnalysisFail; + decl.alive = true; // TODO handle the dependency of this symbol on the decl's vaddr. // If the decl changes vaddr, then this symbol needs to get regenerated. const vaddr = bin_file.getDeclVAddr(decl); @@ -4680,13 +4681,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }, else => { if (typed_value.val.castTag(.decl_ref)) |payload| { + const decl = payload.data; + decl.alive = true; if (self.bin_file.cast(link.File.Elf)) |elf_file| { - const decl = payload.data; const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.MachO)) |macho_file| { - const decl = payload.data; const got_addr = blk: { const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; const got = seg.sections.items[macho_file.got_section_index.?]; @@ -4698,11 +4699,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { }; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Coff)) |coff_file| { - const decl = payload.data; const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; return MCValue{ .memory = got_addr }; } else if (self.bin_file.cast(link.File.Plan9)) |p9| { - const decl = payload.data; const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; return MCValue{ .memory = got_addr }; } else { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a8ec677753..826b73317c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -262,6 +262,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -281,21 +282,7 @@ pub const DeclGen = struct { const decl = val.castTag(.extern_fn).?.data; try writer.print("{s}", .{decl.name}); }, - else => switch (t.ptrSize()) { - .Slice => unreachable, - .Many => unreachable, - .One => { - var arena = std.heap.ArenaAllocator.init(dg.module.gpa); - defer arena.deinit(); - - const elem_ty = t.elemType(); - const elem_val = try val.pointerDeref(&arena.allocator); - - try writer.writeAll("&"); - try dg.renderValue(writer, elem_ty, elem_val); - }, - .C => unreachable, - }, + else => unreachable, }, }, .Array => { @@ -421,6 +408,7 @@ pub const DeclGen = struct { .one => try writer.writeAll("1"), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; + decl.alive = true; // Determine if we must pointer cast. assert(decl.has_tv); @@ -433,11 +421,13 @@ pub const DeclGen = struct { } }, .function => { - const func = val.castTag(.function).?.data; - try writer.print("{s}", .{func.owner_decl.name}); + const decl = val.castTag(.function).?.data.owner_decl; + decl.alive = true; + try writer.print("{s}", .{decl.name}); }, .extern_fn => { const decl = val.castTag(.extern_fn).?.data; + decl.alive = true; try writer.print("{s}", .{decl.name}); }, else => unreachable, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0e9a572bea..961ed7ee99 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -673,17 +673,21 @@ pub const DeclGen = struct { } fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { - const llvm_type = try self.llvmType(tv.ty); - - if (tv.val.isUndef()) + if (tv.val.isUndef()) { + const llvm_type = try self.llvmType(tv.ty); return llvm_type.getUndef(); + } switch (tv.ty.zigTypeTag()) { - .Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), + .Bool => { + const llvm_type = try self.llvmType(tv.ty); + return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(); + }, .Int => { var bigint_space: Value.BigIntSpace = undefined; const bigint = tv.val.toBigInt(&bigint_space); + const llvm_type = try self.llvmType(tv.ty); if (bigint.eqZero()) return llvm_type.constNull(); if (bigint.limbs.len != 1) { @@ -698,12 +702,17 @@ pub const DeclGen = struct { .Pointer => switch (tv.val.tag()) { .decl_ref => { const decl = tv.val.castTag(.decl_ref).?.data; + decl.alive = true; const val = try self.resolveGlobalDecl(decl); + const llvm_type = try self.llvmType(tv.ty); return val.constBitCast(llvm_type); }, .variable => { - const variable = tv.val.castTag(.variable).?.data; - const val = try self.resolveGlobalDecl(variable.owner_decl); + const decl = tv.val.castTag(.variable).?.data.owner_decl; + decl.alive = true; + const val = try self.resolveGlobalDecl(decl); + const llvm_var_type = try self.llvmType(tv.ty); + const llvm_type = llvm_var_type.pointerType(0); return val.constBitCast(llvm_type); }, .slice => { @@ -783,6 +792,7 @@ pub const DeclGen = struct { .decl_ref => tv.val.castTag(.decl_ref).?.data, else => unreachable, }; + fn_decl.alive = true; return self.resolveLlvmFunction(fn_decl); }, .ErrorSet => { @@ -903,9 +913,7 @@ pub const FuncGen = struct { return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); } const inst_index = Air.refToIndex(inst).?; - if (self.func_inst_table.get(inst_index)) |value| return value; - - return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{}); + return self.func_inst_table.get(inst_index).?; } fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { @@ -966,8 +974,8 @@ pub const FuncGen = struct { .struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_val => try self.airStructFieldVal(inst), - .slice_elem_val => try self.airSliceElemVal(inst, false), - .ptr_slice_elem_val => try self.airSliceElemVal(inst, true), + .slice_elem_val => try self.airSliceElemVal(inst), + .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), @@ -1170,11 +1178,20 @@ pub const FuncGen = struct { return self.builder.buildExtractValue(operand, index, ""); } - fn airSliceElemVal( - self: *FuncGen, - inst: Air.Inst.Index, - operand_is_ptr: bool, - ) !?*const llvm.Value { + fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildExtractValue(lhs, 0, ""); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + + fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1182,7 +1199,7 @@ pub const FuncGen = struct { const lhs = try self.resolveInst(bin_op.lhs); const rhs = try self.resolveInst(bin_op.rhs); - const base_ptr = if (!operand_is_ptr) lhs else ptr: { + const base_ptr = ptr: { const index_type = self.context.intType(32); const indices: [2]*const llvm.Value = .{ index_type.constNull(), diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index 37cc6bc59c..2f1632e0fc 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1016,6 +1016,7 @@ pub const Context = struct { .Pointer => { if (val.castTag(.decl_ref)) |payload| { const decl = payload.data; + decl.alive = true; // offset into the offset table within the 'data' section const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 135b59f82b..3b2aae85bc 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -224,7 +224,9 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void { const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; - assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count()); + // TODO I changed this assert from == to >= but this code all needs to be audited; see + // the comment in `freeDecl`. + assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count()); const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; var got_table = try self.base.allocator.alloc(u8, got_size); defer self.base.allocator.free(got_table); @@ -358,11 +360,18 @@ fn addDeclExports( } pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { + // TODO this is not the correct check for being function body, + // it could just be a function pointer. + // TODO audit the lifetimes of decls table entries. It's possible to get + // allocateDeclIndexes and then freeDecl without any updateDecl in between. + // However that is planned to change, see the TODO comment in Module.zig + // in the deleteUnusedDecl function. const is_fn = (decl.ty.zigTypeTag() == .Fn); - if (is_fn) - assert(self.fn_decl_table.swapRemove(decl)) - else - assert(self.data_decl_table.swapRemove(decl)); + if (is_fn) { + _ = self.fn_decl_table.swapRemove(decl); + } else { + _ = self.data_decl_table.swapRemove(decl); + } } pub fn updateDeclExports( diff --git a/src/value.zig b/src/value.zig index d3317ef31d..32be34ee2c 100644 --- a/src/value.zig +++ b/src/value.zig @@ -103,6 +103,7 @@ pub const Value = extern union { /// Represents a comptime variables storage. comptime_alloc, /// Represents a pointer to a decl, not the value of the decl. + /// When machine codegen backend sees this, it must set the Decl's `alive` field to true. decl_ref, elem_ptr, field_ptr, @@ -1346,28 +1347,48 @@ pub const Value = extern union { /// Asserts the value is a pointer and dereferences it. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. - pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { - return switch (self.tag()) { + pub fn pointerDeref( + self: Value, + allocator: *Allocator, + ) error{ AnalysisFail, OutOfMemory }!?Value { + const sub_val: Value = switch (self.tag()) { .comptime_alloc => self.castTag(.comptime_alloc).?.data.val, - .decl_ref => self.castTag(.decl_ref).?.data.value(), - .elem_ptr => { + .decl_ref => try self.castTag(.decl_ref).?.data.value(), + .elem_ptr => blk: { const elem_ptr = self.castTag(.elem_ptr).?.data; - const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); - return array_val.elemValue(allocator, elem_ptr.index); + const array_val = (try elem_ptr.array_ptr.pointerDeref(allocator)) orelse return null; + break :blk try array_val.elemValue(allocator, elem_ptr.index); }, - .field_ptr => { + .field_ptr => blk: { const field_ptr = self.castTag(.field_ptr).?.data; - const container_val = try field_ptr.container_ptr.pointerDeref(allocator); - return container_val.fieldValue(allocator, field_ptr.field_index); + const container_val = (try field_ptr.container_ptr.pointerDeref(allocator)) orelse return null; + break :blk try container_val.fieldValue(allocator, field_ptr.field_index); }, - .eu_payload_ptr => { + .eu_payload_ptr => blk: { const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; - const err_union_val = try err_union_ptr.pointerDeref(allocator); - return err_union_val.castTag(.error_union).?.data; + const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; + break :blk err_union_val.castTag(.error_union).?.data; }, + .zero, + .one, + .int_u64, + .int_i64, + .int_big_positive, + .int_big_negative, + .variable, + .extern_fn, + .function, + => return null, + else => unreachable, }; + if (sub_val.tag() == .variable) { + // This would be loading a runtime value at compile-time so we return + // the indicator that this pointer dereference requires being done at runtime. + return null; + } + return sub_val; } pub fn sliceLen(val: Value) u64 { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 6427e2e3b8..4f7d80d1fa 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -49,7 +49,7 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn foo() callconv(y) c_int { \\ return 0; \\} - \\var y: i32 = 1234; + \\var y: @import("std").builtin.CallingConvention = .C; , &.{ ":2:22: error: unable to resolve comptime value", ":5:26: error: unable to resolve comptime value", -- cgit v1.2.3 From f6b1fa9e29ccab77a54e92dc13a7eb8e407bdbbc Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 30 Jul 2021 11:43:17 +0200 Subject: stage2 codegen: genTypedValue for error unions and error sets --- src/codegen.zig | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'src/codegen.zig') diff --git a/src/codegen.zig b/src/codegen.zig index d16a87adca..0917f2c847 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -19,7 +19,6 @@ const DW = std.dwarf; const leb128 = std.leb; const log = std.log.scoped(.codegen); const build_options = @import("build_options"); -const LazySrcLoc = Module.LazySrcLoc; const RegisterManager = @import("register_manager.zig").RegisterManager; const X8664Encoder = @import("codegen/x86_64.zig").Encoder; @@ -4741,6 +4740,33 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } return self.fail("TODO non pointer optionals", .{}); }, + .ErrorSet => { + switch (typed_value.val.tag()) { + .@"error" => { + const err_name = typed_value.val.castTag(.@"error").?.data.name; + const module = self.bin_file.options.module.?; + const global_error_set = module.global_error_set; + const error_index = global_error_set.get(err_name).?; + return MCValue{ .immediate = error_index }; + }, + else => { + // In this case we are rendering an error union which has a 0 bits payload. + return MCValue{ .immediate = 0 }; + }, + } + }, + .ErrorUnion => { + const error_type = typed_value.ty.errorUnionSet(); + const payload_type = typed_value.ty.errorUnionPayload(); + const sub_val = typed_value.val.castTag(.error_union).?.data; + + if (!payload_type.hasCodeGenBits()) { + // We use the error type directly as the type. + return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); + } + + return self.fail("TODO implement error union const of type '{}'", .{typed_value.ty}); + }, else => return self.fail("TODO implement const of type '{}'", .{typed_value.ty}), } } -- cgit v1.2.3 From 84039a57e4684e8df10e657bb76c6acb3fb89238 Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Fri, 30 Jul 2021 22:48:20 +0200 Subject: stage2 codegen: Implement genTypedValue for enums --- src/codegen.zig | 23 +++++++++++++++++++++++ test/stage2/arm.zig | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'src/codegen.zig') diff --git a/src/codegen.zig b/src/codegen.zig index 0917f2c847..bc3ff6257c 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4740,6 +4740,29 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } return self.fail("TODO non pointer optionals", .{}); }, + .Enum => { + if (typed_value.val.castTag(.enum_field_index)) |field_index| { + switch (typed_value.ty.tag()) { + .enum_simple => { + return MCValue{ .immediate = field_index.data }; + }, + .enum_full, .enum_nonexhaustive => { + const enum_full = typed_value.ty.cast(Type.Payload.EnumFull).?.data; + if (enum_full.values.count() != 0) { + const tag_val = enum_full.values.keys()[field_index.data]; + return self.genTypedValue(.{ .ty = enum_full.tag_ty, .val = tag_val }); + } else { + return MCValue{ .immediate = field_index.data }; + } + }, + else => unreachable, + } + } else { + var int_tag_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = typed_value.ty.intTagType(&int_tag_buffer); + return self.genTypedValue(.{ .ty = int_tag_ty, .val = typed_value.val }); + } + }, .ErrorSet => { switch (typed_value.val.tag()) { .@"error" => { diff --git a/test/stage2/arm.zig b/test/stage2/arm.zig index 6b4f569757..103b058a54 100644 --- a/test/stage2/arm.zig +++ b/test/stage2/arm.zig @@ -299,6 +299,28 @@ pub fn addCases(ctx: *TestContext) !void { ); } + { + var case = ctx.exe("enums", linux_arm); + case.addCompareOutput( + \\const Number = enum { one, two, three }; + \\ + \\pub fn main() void { + \\ var x: Number = .one; + \\ var y = Number.two; + \\ var z = @intToEnum(Number, 2); + \\ assert(@enumToInt(x) == 0); + \\ assert(@enumToInt(y) == 1); + \\ assert(@enumToInt(z) == 2); + \\} + \\ + \\fn assert(ok: bool) void { + \\ if (!ok) unreachable; // assertion failure + \\} + , + "", + ); + } + { var case = ctx.exe("recursive fibonacci", linux_arm); case.addCompareOutput( -- cgit v1.2.3 From 1f95c50d9a0c4c057780d387d57ac2ac40df1720 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jul 2021 17:48:24 -0700 Subject: codegen: cmp lowering treats bools the same as unsigned int fixes a crash when lowering `a == b` and they are of type bool. I'm not worried about floats; I think we will probably add separate AIR instructions for floats. --- src/codegen.zig | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/codegen.zig') diff --git a/src/codegen.zig b/src/codegen.zig index bc3ff6257c..77672e82b0 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2889,10 +2889,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { const src_mcv = try self.limitImmediateType(bin_op.rhs, i32); try self.genX8664BinMathCode(Type.initTag(.bool), dst_mcv, src_mcv, 7, 0x38); - const info = ty.intInfo(self.target.*); - break :result switch (info.signedness) { - .signed => MCValue{ .compare_flags_signed = op }, - .unsigned => MCValue{ .compare_flags_unsigned = op }, + break :result switch (ty.isSignedInt()) { + true => MCValue{ .compare_flags_signed = op }, + false => MCValue{ .compare_flags_unsigned = op }, }; }, .arm, .armeb => result: { @@ -2934,10 +2933,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { // The destination register is not present in the cmp instruction try self.genArmBinOpCode(undefined, lhs_mcv, rhs_mcv, false, .cmp_eq); - const info = ty.intInfo(self.target.*); - break :result switch (info.signedness) { - .signed => MCValue{ .compare_flags_signed = op }, - .unsigned => MCValue{ .compare_flags_unsigned = op }, + break :result switch (ty.isSignedInt()) { + true => MCValue{ .compare_flags_signed = op }, + false => MCValue{ .compare_flags_unsigned = op }, }; }, else => return self.fail("TODO implement cmp for {}", .{self.target.cpu.arch}), -- cgit v1.2.3 From 0d09c6aed8811ded3e6dcb62fa9539d3795cf97b Mon Sep 17 00:00:00 2001 From: joachimschmidt557 Date: Sat, 31 Jul 2021 12:52:35 +0200 Subject: stage2 ARM: fix stack alignment Acording to the AAPCS32, the stack alignment at public interfaces should be 8, not 4. --- src/codegen.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/codegen.zig') diff --git a/src/codegen.zig b/src/codegen.zig index 77672e82b0..8c56ab4431 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4916,7 +4916,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } result.stack_byte_count = nsaa; - result.stack_align = 4; + result.stack_align = 8; }, else => return self.fail("TODO implement function parameters for {} on arm", .{cc}), } -- cgit v1.2.3 From ddf14323ea9b2c75ac5ed286525d27730a192b53 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Aug 2021 16:13:58 -0700 Subject: stage2: implement `@truncate` --- src/Air.zig | 11 +- src/Liveness.zig | 1 + src/Module.zig | 238 ------------------------------------------ src/Sema.zig | 104 ++++++++++++------ src/codegen.zig | 14 +++ src/codegen/c.zig | 15 ++- src/codegen/llvm.zig | 11 ++ src/codegen/llvm/bindings.zig | 8 ++ src/print_air.zig | 1 + src/value.zig | 238 ++++++++++++++++++++++++++++++++++++++++++ test/behavior/basic.zig | 11 ++ test/behavior/misc.zig | 7 -- 12 files changed, 380 insertions(+), 279 deletions(-) (limited to 'src/codegen.zig') diff --git a/src/Air.zig b/src/Air.zig index d202c079bc..d923bf0b02 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -206,10 +206,16 @@ pub const Inst = struct { /// Convert from one float type to another. /// Uses the `ty_op` field. floatcast, - /// TODO audit uses of this. We should have explicit instructions for integer - /// widening and truncating. + /// Returns an integer with a different type than the operand. The new type may have + /// fewer, the same, or more bits than the operand type. However, the instruction + /// guarantees that the same integer value fits in both types. + /// See `trunc` for integer truncation. /// Uses the `ty_op` field. intcast, + /// Truncate higher bits from an integer, resulting in an integer with the same + /// sign but an equal or smaller number of bits. + /// Uses the `ty_op` field. + trunc, /// ?T => T. If the value is null, undefined behavior. /// Uses the `ty_op` field. optional_payload, @@ -452,6 +458,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Liveness.zig b/src/Liveness.zig index 7ba062fa31..4e22febc5a 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -264,6 +264,7 @@ fn analyzeInst( .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/Module.zig b/src/Module.zig index d87f20621c..84b721369d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -4033,244 +4033,6 @@ pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) Co return error.AnalysisFail; } -pub fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // 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.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try 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 (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // 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.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try 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.sub(lhs_bigint, rhs_bigint); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intDiv(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // 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.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs_q = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - const limbs_r = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len, - ); - const limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), - ); - var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; - var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; - result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); - const result_limbs = result_q.limbs[0..result_q.len]; - - if (result_q.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value { - // 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.toBigInt(&lhs_space); - const rhs_bigint = rhs.toBigInt(&rhs_space); - const limbs = try allocator.alloc( - std.math.big.Limb, - lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, - ); - var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; - var limbs_buffer = try allocator.alloc( - std.math.big.Limb, - std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), - ); - defer allocator.free(limbs_buffer); - result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); - const result_limbs = result_bigint.limbs[0..result_bigint.len]; - - if (result_bigint.positive) { - return Value.Tag.int_big_positive.create(allocator, result_limbs); - } else { - return Value.Tag.int_big_negative.create(allocator, result_limbs); - } -} - -pub fn floatAdd( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val + rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val + rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val + rhs_val); - }, - else => unreachable, - } -} - -pub fn floatSub( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val - rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val - rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val - rhs_val); - }, - else => unreachable, - } -} - -pub fn floatDiv( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val / rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val / rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val / rhs_val); - }, - else => unreachable, - } -} - -pub fn floatMul( - arena: *Allocator, - float_type: Type, - src: LazySrcLoc, - lhs: Value, - rhs: Value, -) !Value { - _ = src; - switch (float_type.tag()) { - .f16 => { - @panic("TODO add __trunctfhf2 to compiler-rt"); - //const lhs_val = lhs.toFloat(f16); - //const rhs_val = rhs.toFloat(f16); - //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); - }, - .f32 => { - const lhs_val = lhs.toFloat(f32); - const rhs_val = rhs.toFloat(f32); - return Value.Tag.float_32.create(arena, lhs_val * rhs_val); - }, - .f64 => { - const lhs_val = lhs.toFloat(f64); - const rhs_val = rhs.toFloat(f64); - return Value.Tag.float_64.create(arena, lhs_val * rhs_val); - }, - .f128, .comptime_float, .c_longdouble => { - const lhs_val = lhs.toFloat(f128); - const rhs_val = rhs.toFloat(f128); - return Value.Tag.float_128.create(arena, lhs_val * rhs_val); - }, - else => unreachable, - } -} - pub fn simplePtrType( arena: *Allocator, elem_ty: Type, diff --git a/src/Sema.zig b/src/Sema.zig index 5ad590be6a..6b281f8569 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3479,27 +3479,8 @@ fn zirIntCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr const dest_type = try sema.resolveType(block, dest_ty_src, extra.lhs); const operand = sema.resolveInst(extra.rhs); - const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { - .ComptimeInt => true, - .Int => false, - else => return sema.mod.fail( - &block.base, - dest_ty_src, - "expected integer type, found '{}'", - .{dest_type}, - ), - }; - - const operand_ty = sema.typeOf(operand); - switch (operand_ty.zigTypeTag()) { - .ComptimeInt, .Int => {}, - else => return sema.mod.fail( - &block.base, - operand_src, - "expected integer type, found '{}'", - .{operand_ty}, - ), - } + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_type); + _ = try sema.requireIntegerType(block, operand_src, sema.typeOf(operand)); if (try sema.isComptimeKnown(block, operand_src, operand)) { return sema.coerce(block, dest_type, operand, operand_src); @@ -4951,30 +4932,30 @@ fn analyzeArithmetic( const value = switch (zir_tag) { .add => blk: { const val = if (is_int) - try Module.intAdd(sema.arena, lhs_val, rhs_val) + try lhs_val.intAdd(rhs_val, sema.arena) else - try Module.floatAdd(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); break :blk val; }, .sub => blk: { const val = if (is_int) - try Module.intSub(sema.arena, lhs_val, rhs_val) + try lhs_val.intSub(rhs_val, sema.arena) else - try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); break :blk val; }, .div => blk: { const val = if (is_int) - try Module.intDiv(sema.arena, lhs_val, rhs_val) + try lhs_val.intDiv(rhs_val, sema.arena) else - try Module.floatDiv(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); break :blk val; }, .mul => blk: { const val = if (is_int) - try Module.intMul(sema.arena, lhs_val, rhs_val) + try lhs_val.intMul(rhs_val, sema.arena) else - try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val); + try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); break :blk val; }, else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}), @@ -6173,7 +6154,62 @@ fn zirPtrCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileErr fn zirTruncate(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirTruncate", .{}); + const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + const mod = sema.mod; + const dest_is_comptime_int = try sema.requireIntegerType(block, dest_ty_src, dest_ty); + const src_is_comptime_int = try sema.requireIntegerType(block, operand_src, operand_ty); + + if (dest_is_comptime_int) { + return sema.coerce(block, dest_ty, operand, operand_src); + } + + const target = mod.getTarget(); + const src_info = operand_ty.intInfo(target); + const dest_info = dest_ty.intInfo(target); + + if (src_info.bits == 0 or dest_info.bits == 0) { + return sema.addConstant(dest_ty, Value.initTag(.zero)); + } + + if (!src_is_comptime_int) { + if (src_info.signedness != dest_info.signedness) { + return mod.fail(&block.base, operand_src, "expected {s} integer type, found '{}'", .{ + @tagName(dest_info.signedness), operand_ty, + }); + } + if (src_info.bits > 0 and src_info.bits < dest_info.bits) { + const msg = msg: { + const msg = try mod.errMsg( + &block.base, + src, + "destination type '{}' has more bits than source type '{}'", + .{ dest_ty, operand_ty }, + ); + errdefer msg.destroy(mod.gpa); + try mod.errNote(&block.base, dest_ty_src, msg, "destination type has {d} bits", .{ + dest_info.bits, + }); + try mod.errNote(&block.base, operand_src, msg, "source type has {d} bits", .{ + src_info.bits, + }); + break :msg msg; + }; + return mod.failWithOwnedErrorMsg(&block.base, msg); + } + } + + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(dest_ty); + return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.bits)); + } + + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.trunc, dest_ty, operand); } fn zirAlignCast(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6594,6 +6630,14 @@ fn requireRuntimeBlock(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) !void try sema.requireFunctionBlock(block, src); } +fn requireIntegerType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !bool { + switch (ty.zigTypeTag()) { + .ComptimeInt => return true, + .Int => return false, + else => return sema.mod.fail(&block.base, src, "expected integer type, found '{}'", .{ty}), + } +} + fn validateVarType(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) !void { if (!ty.isValidVarType(false)) { return sema.mod.fail(&block.base, src, "variable of type '{}' must be const or comptime", .{ty}); diff --git a/src/codegen.zig b/src/codegen.zig index 8c56ab4431..3864479d2d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -835,6 +835,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .dbg_stmt => try self.airDbgStmt(inst), .floatcast => try self.airFloatCast(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .bool_to_int => try self.airBoolToInt(inst), .is_non_null => try self.airIsNonNull(inst), .is_non_null_ptr => try self.airIsNonNullPtr(inst), @@ -1109,6 +1110,19 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); } + fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + if (self.liveness.isUnused(inst)) + return self.finishAir(inst, .dead, .{ ty_op.operand, .none, .none }); + + const operand = try self.resolveInst(ty_op.operand); + _ = operand; + const result: MCValue = switch (arch) { + else => return self.fail("TODO implement trunc for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ ty_op.operand, .none, .none }); + } + fn airBoolToInt(self: *Self, inst: Air.Inst.Index) !void { const un_op = self.air.instructions.items(.data)[inst].un_op; const operand = try self.resolveInst(un_op); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 826b73317c..22420aca45 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -900,6 +900,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .call => try airCall(o, inst), .dbg_stmt => try airDbgStmt(o, inst), .intcast => try airIntCast(o, inst), + .trunc => try airTrunc(o, inst), .bool_to_int => try airBoolToInt(o, inst), .load => try airLoad(o, inst), .ret => try airRet(o, inst), @@ -1038,7 +1039,7 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { return CValue.none; const ty_op = o.air.instructions.items(.data)[inst].ty_op; - const from = try o.resolveInst(ty_op.operand); + const operand = try o.resolveInst(ty_op.operand); const writer = o.writer(); const inst_ty = o.air.typeOfIndex(inst); @@ -1046,11 +1047,21 @@ fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue { try writer.writeAll(" = ("); try o.dg.renderType(writer, inst_ty); try writer.writeAll(")"); - try o.writeCValue(writer, from); + try o.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } +fn airTrunc(o: *Object, inst: Air.Inst.Index) !CValue { + if (o.liveness.isUnused(inst)) + return CValue.none; + + const ty_op = o.air.instructions.items(.data)[inst].ty_op; + const operand = try o.resolveInst(ty_op.operand); + _ = operand; + return o.dg.fail("TODO: C backend: airTrunc", .{}); +} + fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue { if (o.liveness.isUnused(inst)) return CValue.none; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 961ed7ee99..8b3edcfe23 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -960,6 +960,7 @@ pub const FuncGen = struct { .call => try self.airCall(inst), .cond_br => try self.airCondBr(inst), .intcast => try self.airIntCast(inst), + .trunc => try self.airTrunc(inst), .floatcast => try self.airFloatCast(inst), .ptrtoint => try self.airPtrToInt(inst), .load => try self.airLoad(inst), @@ -1615,6 +1616,16 @@ pub const FuncGen = struct { return self.builder.buildIntCast2(operand, try self.dg.llvmType(inst_ty), llvm.Bool.fromBool(signed), ""); } + fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const ty_op = self.air.instructions.items(.data)[inst].ty_op; + const operand = try self.resolveInst(ty_op.operand); + const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst)); + return self.builder.buildTrunc(operand, dest_llvm_ty, ""); + } + fn airFloatCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 0977d6128d..4af3cadd84 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -423,6 +423,14 @@ pub const Builder = opaque { Idx: c_uint, Name: [*:0]const u8, ) *const Value; + + pub const buildTrunc = LLVMBuildTrunc; + extern fn LLVMBuildTrunc( + *const Builder, + Val: *const Value, + DestTy: *const Type, + Name: [*:0]const u8, + ) *const Value; }; pub const IntPredicate = enum(c_int) { diff --git a/src/print_air.zig b/src/print_air.zig index 5d77c303bb..00317b26e8 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -151,6 +151,7 @@ const Writer = struct { .load, .floatcast, .intcast, + .trunc, .optional_payload, .optional_payload_ptr, .wrap_optional, diff --git a/src/value.zig b/src/value.zig index a0a7c0650c..134b51e494 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1407,6 +1407,244 @@ pub const Value = extern union { }; } + pub fn intAdd(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // 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.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try 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 (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intSub(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // 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.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try 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.sub(lhs_bigint, rhs_bigint); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intDiv(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // 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.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len), + ); + var result_q = BigIntMutable{ .limbs = limbs_q, .positive = undefined, .len = undefined }; + var result_r = BigIntMutable{ .limbs = limbs_r, .positive = undefined, .len = undefined }; + result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_q.limbs[0..result_q.len]; + + if (result_q.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // 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.toBigInt(&lhs_space); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + var limbs_buffer = try allocator.alloc( + std.math.big.Limb, + std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1), + ); + defer allocator.free(limbs_buffer); + result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intTrunc(val: Value, arena: *Allocator, bits: u16) !Value { + const x = val.toUnsignedInt(); // TODO: implement comptime truncate on big ints + if (bits == 64) return val; + const mask = (@as(u64, 1) << @intCast(u6, bits)) - 1; + const truncated = x & mask; + return Tag.int_u64.create(arena, truncated); + } + + pub fn floatAdd( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val + rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val + rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val + rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val + rhs_val); + }, + else => unreachable, + } + } + + pub fn floatSub( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val - rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val - rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val - rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val - rhs_val); + }, + else => unreachable, + } + } + + pub fn floatDiv( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val / rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val / rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val / rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val / rhs_val); + }, + else => unreachable, + } + } + + pub fn floatMul( + lhs: Value, + rhs: Value, + float_type: Type, + arena: *Allocator, + ) !Value { + switch (float_type.tag()) { + .f16 => { + @panic("TODO add __trunctfhf2 to compiler-rt"); + //const lhs_val = lhs.toFloat(f16); + //const rhs_val = rhs.toFloat(f16); + //return Value.Tag.float_16.create(arena, lhs_val * rhs_val); + }, + .f32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(arena, lhs_val * rhs_val); + }, + .f64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(arena, lhs_val * rhs_val); + }, + .f128, .comptime_float, .c_longdouble => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(arena, lhs_val * rhs_val); + }, + else => unreachable, + } + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 8f0f20cdf5..87de1c9fe5 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const expect = std.testing.expect; + // normal comment /// this is a documentation comment @@ -7,3 +10,11 @@ fn emptyFunctionWithComments() void {} test "empty function with comments" { emptyFunctionWithComments(); } + +test "truncate" { + try expect(testTruncate(0x10fd) == 0xfd); + comptime try expect(testTruncate(0x10fd) == 0xfd); +} +fn testTruncate(x: u32) u8 { + return @truncate(u8, x); +} diff --git a/test/behavior/misc.zig b/test/behavior/misc.zig index ba38d6327c..700e043313 100644 --- a/test/behavior/misc.zig +++ b/test/behavior/misc.zig @@ -5,13 +5,6 @@ const expectEqualStrings = std.testing.expectEqualStrings; const mem = std.mem; const builtin = @import("builtin"); -test "truncate" { - try expect(testTruncate(0x10fd) == 0xfd); -} -fn testTruncate(x: u32) u8 { - return @truncate(u8, x); -} - fn first4KeysOfHomeRow() []const u8 { return "aoeu"; } -- cgit v1.2.3 From d31352ee85d633876877d87b813cd3611aa17d88 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 6 Aug 2021 02:01:47 -0700 Subject: Update all usages of mem.split/mem.tokenize for generic version --- build.zig | 10 +++++----- lib/std/SemanticVersion.zig | 10 +++++----- lib/std/build.zig | 8 ++++---- lib/std/builtin.zig | 2 +- lib/std/child_process.zig | 4 ++-- lib/std/fs.zig | 2 +- lib/std/fs/path.zig | 26 +++++++++++++------------- lib/std/net.zig | 12 ++++++------ lib/std/os.zig | 2 +- lib/std/process.zig | 2 +- lib/std/zig/cross_target.zig | 10 +++++----- lib/std/zig/system.zig | 6 +++--- src/Cache.zig | 4 ++-- src/Compilation.zig | 4 ++-- src/codegen.zig | 2 +- src/glibc.zig | 14 +++++++------- src/libc_installation.zig | 10 +++++----- src/link/MachO/Dylib.zig | 2 +- src/main.zig | 4 ++-- src/test.zig | 4 ++-- test/behavior/bugs/6456.zig | 2 +- test/tests.zig | 2 +- tools/update_glibc.zig | 8 ++++---- tools/update_spirv_features.zig | 2 +- 24 files changed, 76 insertions(+), 76 deletions(-) (limited to 'src/codegen.zig') diff --git a/build.zig b/build.zig index f60cd573a4..449c6a7f1a 100644 --- a/build.zig +++ b/build.zig @@ -187,7 +187,7 @@ pub fn build(b: *Builder) !void { }, 2 => { // Untagged development build (e.g. 0.8.0-684-gbbe2cca1a). - var it = mem.split(git_describe, "-"); + var it = mem.split(u8, git_describe, "-"); const tagged_ancestor = it.next() orelse unreachable; const commit_height = it.next() orelse unreachable; const commit_id = it.next() orelse unreachable; @@ -479,7 +479,7 @@ fn addCxxKnownPath( ctx.cxx_compiler, b.fmt("-print-file-name={s}", .{objname}), }); - const path_unpadded = mem.tokenize(path_padded, "\r\n").next().?; + const path_unpadded = mem.tokenize(u8, path_padded, "\r\n").next().?; if (mem.eql(u8, path_unpadded, objname)) { if (errtxt) |msg| { warn("{s}", .{msg}); @@ -502,7 +502,7 @@ fn addCxxKnownPath( } fn addCMakeLibraryList(exe: *std.build.LibExeObjStep, list: []const u8) void { - var it = mem.tokenize(list, ";"); + var it = mem.tokenize(u8, list, ";"); while (it.next()) |lib| { if (mem.startsWith(u8, lib, "-l")) { exe.linkSystemLibrary(lib["-l".len..]); @@ -596,11 +596,11 @@ fn findAndParseConfigH(b: *Builder, config_h_path_option: ?[]const u8) ?CMakeCon }, }; - var lines_it = mem.tokenize(config_h_text, "\r\n"); + var lines_it = mem.tokenize(u8, config_h_text, "\r\n"); while (lines_it.next()) |line| { inline for (mappings) |mapping| { if (mem.startsWith(u8, line, mapping.prefix)) { - var it = mem.split(line, "\""); + var it = mem.split(u8, line, "\""); _ = it.next().?; // skip the stuff before the quote const quoted = it.next().?; // the stuff inside the quote @field(ctx, mapping.field) = toNativePathSep(b, quoted); diff --git a/lib/std/SemanticVersion.zig b/lib/std/SemanticVersion.zig index e029952509..17d5b65571 100644 --- a/lib/std/SemanticVersion.zig +++ b/lib/std/SemanticVersion.zig @@ -48,8 +48,8 @@ pub fn order(lhs: Version, rhs: Version) std.math.Order { if (lhs.pre == null and rhs.pre != null) return .gt; // Iterate over pre-release identifiers until a difference is found. - var lhs_pre_it = std.mem.split(lhs.pre.?, "."); - var rhs_pre_it = std.mem.split(rhs.pre.?, "."); + var lhs_pre_it = std.mem.split(u8, lhs.pre.?, "."); + var rhs_pre_it = std.mem.split(u8, rhs.pre.?, "."); while (true) { const next_lid = lhs_pre_it.next(); const next_rid = rhs_pre_it.next(); @@ -92,7 +92,7 @@ pub fn parse(text: []const u8) !Version { // Parse the required major, minor, and patch numbers. const extra_index = std.mem.indexOfAny(u8, text, "-+"); const required = text[0..(extra_index orelse text.len)]; - var it = std.mem.split(required, "."); + var it = std.mem.split(u8, required, "."); var ver = Version{ .major = try parseNum(it.next() orelse return error.InvalidVersion), .minor = try parseNum(it.next() orelse return error.InvalidVersion), @@ -114,7 +114,7 @@ pub fn parse(text: []const u8) !Version { // Check validity of optional pre-release identifiers. // See: https://semver.org/#spec-item-9 if (ver.pre) |pre| { - it = std.mem.split(pre, "."); + it = std.mem.split(u8, pre, "."); while (it.next()) |id| { // Identifiers MUST NOT be empty. if (id.len == 0) return error.InvalidVersion; @@ -133,7 +133,7 @@ pub fn parse(text: []const u8) !Version { // Check validity of optional build metadata identifiers. // See: https://semver.org/#spec-item-10 if (ver.build) |build| { - it = std.mem.split(build, "."); + it = std.mem.split(u8, build, "."); while (it.next()) |id| { // Identifiers MUST NOT be empty. if (id.len == 0) return error.InvalidVersion; diff --git a/lib/std/build.zig b/lib/std/build.zig index a88667a613..17cad016e8 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1085,7 +1085,7 @@ pub const Builder = struct { if (fs.path.isAbsolute(name)) { return name; } - var it = mem.tokenize(PATH, &[_]u8{fs.path.delimiter}); + var it = mem.tokenize(u8, PATH, &[_]u8{fs.path.delimiter}); while (it.next()) |path| { const full_path = try fs.path.join(self.allocator, &[_][]const u8{ path, @@ -1211,10 +1211,10 @@ pub const Builder = struct { const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); var list = ArrayList(PkgConfigPkg).init(self.allocator); errdefer list.deinit(); - var line_it = mem.tokenize(stdout, "\r\n"); + var line_it = mem.tokenize(u8, stdout, "\r\n"); while (line_it.next()) |line| { if (mem.trim(u8, line, " \t").len == 0) continue; - var tok_it = mem.tokenize(line, " \t"); + var tok_it = mem.tokenize(u8, line, " \t"); try list.append(PkgConfigPkg{ .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, .desc = tok_it.rest(), @@ -1872,7 +1872,7 @@ pub const LibExeObjStep = struct { error.FileNotFound => return error.PkgConfigNotInstalled, else => return err, }; - var it = mem.tokenize(stdout, " \r\n\t"); + var it = mem.tokenize(u8, stdout, " \r\n\t"); while (it.next()) |tok| { if (mem.eql(u8, tok, "-I")) { const dir = it.next() orelse return error.PkgConfigInvalidOutput; diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 9d432a3a00..6643e07837 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -509,7 +509,7 @@ pub const Version = struct { // found no digits or '.' before unexpected character if (end == 0) return error.InvalidVersion; - var it = std.mem.split(text[0..end], "."); + var it = std.mem.split(u8, text[0..end], "."); // substring is not empty, first call will succeed const major = it.next().?; if (major.len == 0) return error.InvalidVersion; diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index b63153e904..3f3fa9c754 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -836,12 +836,12 @@ pub const ChildProcess = struct { const app_name = self.argv[0]; - var it = mem.tokenize(PATH, ";"); + var it = mem.tokenize(u8, PATH, ";"); retry: while (it.next()) |search_path| { const path_no_ext = try fs.path.join(self.allocator, &[_][]const u8{ search_path, app_name }); defer self.allocator.free(path_no_ext); - var ext_it = mem.tokenize(PATHEXT, ";"); + var ext_it = mem.tokenize(u8, PATHEXT, ";"); while (ext_it.next()) |app_ext| { const joined_path = try mem.concat(self.allocator, u8, &[_][]const u8{ path_no_ext, app_ext }); defer self.allocator.free(joined_path); diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 2504965e3a..3c5399e6a4 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -2455,7 +2455,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { } else if (argv0.len != 0) { // argv[0] is not empty (and not a path): search it inside PATH const PATH = std.os.getenvZ("PATH") orelse return error.FileNotFound; - var path_it = mem.tokenize(PATH, &[_]u8{path.delimiter}); + var path_it = mem.tokenize(u8, PATH, &[_]u8{path.delimiter}); while (path_it.next()) |a_path| { var resolved_path_buf: [MAX_PATH_BYTES - 1:0]u8 = undefined; const resolved_path = std.fmt.bufPrintZ(&resolved_path_buf, "{s}/{s}", .{ diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index ed04f0b09d..baaadcfe59 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -345,7 +345,7 @@ pub fn windowsParsePath(path: []const u8) WindowsPath { return relative_path; } - var it = mem.tokenize(path, &[_]u8{this_sep}); + var it = mem.tokenize(u8, path, &[_]u8{this_sep}); _ = (it.next() orelse return relative_path); _ = (it.next() orelse return relative_path); return WindowsPath{ @@ -407,8 +407,8 @@ fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool { const sep1 = ns1[0]; const sep2 = ns2[0]; - var it1 = mem.tokenize(ns1, &[_]u8{sep1}); - var it2 = mem.tokenize(ns2, &[_]u8{sep2}); + var it1 = mem.tokenize(u8, ns1, &[_]u8{sep1}); + var it2 = mem.tokenize(u8, ns2, &[_]u8{sep2}); // TODO ASCII is wrong, we actually need full unicode support to compare paths. return asciiEqlIgnoreCase(it1.next().?, it2.next().?); @@ -428,8 +428,8 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8 const sep1 = p1[0]; const sep2 = p2[0]; - var it1 = mem.tokenize(p1, &[_]u8{sep1}); - var it2 = mem.tokenize(p2, &[_]u8{sep2}); + var it1 = mem.tokenize(u8, p1, &[_]u8{sep1}); + var it2 = mem.tokenize(u8, p2, &[_]u8{sep2}); // TODO ASCII is wrong, we actually need full unicode support to compare paths. return asciiEqlIgnoreCase(it1.next().?, it2.next().?) and asciiEqlIgnoreCase(it1.next().?, it2.next().?); @@ -551,7 +551,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { }, WindowsPath.Kind.NetworkShare => { result = try allocator.alloc(u8, max_size); - var it = mem.tokenize(paths[first_index], "/\\"); + var it = mem.tokenize(u8, paths[first_index], "/\\"); const server_name = it.next().?; const other_name = it.next().?; @@ -618,7 +618,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (!correct_disk_designator) { continue; } - var it = mem.tokenize(p[parsed.disk_designator.len..], "/\\"); + var it = mem.tokenize(u8, p[parsed.disk_designator.len..], "/\\"); while (it.next()) |component| { if (mem.eql(u8, component, ".")) { continue; @@ -687,7 +687,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { errdefer allocator.free(result); for (paths[first_index..]) |p| { - var it = mem.tokenize(p, "/"); + var it = mem.tokenize(u8, p, "/"); while (it.next()) |component| { if (mem.eql(u8, component, ".")) { continue; @@ -1101,8 +1101,8 @@ pub fn relativeWindows(allocator: *Allocator, from: []const u8, to: []const u8) return resolved_to; } - var from_it = mem.tokenize(resolved_from, "/\\"); - var to_it = mem.tokenize(resolved_to, "/\\"); + var from_it = mem.tokenize(u8, resolved_from, "/\\"); + var to_it = mem.tokenize(u8, resolved_to, "/\\"); while (true) { const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); const to_rest = to_it.rest(); @@ -1131,7 +1131,7 @@ pub fn relativeWindows(allocator: *Allocator, from: []const u8, to: []const u8) // shave off the trailing slash result_index -= 1; - var rest_it = mem.tokenize(to_rest, "/\\"); + var rest_it = mem.tokenize(u8, to_rest, "/\\"); while (rest_it.next()) |to_component| { result[result_index] = '\\'; result_index += 1; @@ -1152,8 +1152,8 @@ pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![ const resolved_to = try resolvePosix(allocator, &[_][]const u8{to}); defer allocator.free(resolved_to); - var from_it = mem.tokenize(resolved_from, "/"); - var to_it = mem.tokenize(resolved_to, "/"); + var from_it = mem.tokenize(u8, resolved_from, "/"); + var to_it = mem.tokenize(u8, resolved_to, "/"); while (true) { const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); const to_rest = to_it.rest(); diff --git a/lib/std/net.zig b/lib/std/net.zig index 46d9bee15d..1b53399fd1 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -1130,9 +1130,9 @@ fn linuxLookupNameFromHosts( }, else => |e| return e, }) |line| { - const no_comment_line = mem.split(line, "#").next().?; + const no_comment_line = mem.split(u8, line, "#").next().?; - var line_it = mem.tokenize(no_comment_line, " \t"); + var line_it = mem.tokenize(u8, no_comment_line, " \t"); const ip_text = line_it.next() orelse continue; var first_name_text: ?[]const u8 = null; while (line_it.next()) |name_text| { @@ -1211,7 +1211,7 @@ fn linuxLookupNameFromDnsSearch( mem.copy(u8, canon.items, canon_name); try canon.append('.'); - var tok_it = mem.tokenize(search, " \t"); + var tok_it = mem.tokenize(u8, search, " \t"); while (tok_it.next()) |tok| { canon.shrinkRetainingCapacity(canon_name.len + 1); try canon.appendSlice(tok); @@ -1328,13 +1328,13 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void { }, else => |e| return e, }) |line| { - const no_comment_line = mem.split(line, "#").next().?; - var line_it = mem.tokenize(no_comment_line, " \t"); + const no_comment_line = mem.split(u8, line, "#").next().?; + var line_it = mem.tokenize(u8, no_comment_line, " \t"); const token = line_it.next() orelse continue; if (mem.eql(u8, token, "options")) { while (line_it.next()) |sub_tok| { - var colon_it = mem.split(sub_tok, ":"); + var colon_it = mem.split(u8, sub_tok, ":"); const name = colon_it.next().?; const value_txt = colon_it.next() orelse continue; const value = std.fmt.parseInt(u8, value_txt, 10) catch |err| switch (err) { diff --git a/lib/std/os.zig b/lib/std/os.zig index 4fd0998b0d..9a1c584b75 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1378,7 +1378,7 @@ pub fn execvpeZ_expandArg0( // Use of MAX_PATH_BYTES here is valid as the path_buf will be passed // directly to the operating system in execveZ. var path_buf: [MAX_PATH_BYTES]u8 = undefined; - var it = mem.tokenize(PATH, ":"); + var it = mem.tokenize(u8, PATH, ":"); var seen_eacces = false; var err: ExecveError = undefined; diff --git a/lib/std/process.zig b/lib/std/process.zig index 4e875e7ebd..3690b67a14 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -109,7 +109,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { for (environ) |env| { const pair = mem.spanZ(env); - var parts = mem.split(pair, "="); + var parts = mem.split(u8, pair, "="); const key = parts.next().?; const value = parts.next().?; try result.put(key, value); diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index 1058628633..4a8879db57 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -233,7 +233,7 @@ pub const CrossTarget = struct { .dynamic_linker = DynamicLinker.init(args.dynamic_linker), }; - var it = mem.split(args.arch_os_abi, "-"); + var it = mem.split(u8, args.arch_os_abi, "-"); const arch_name = it.next().?; const arch_is_native = mem.eql(u8, arch_name, "native"); if (!arch_is_native) { @@ -251,7 +251,7 @@ pub const CrossTarget = struct { const opt_abi_text = it.next(); if (opt_abi_text) |abi_text| { - var abi_it = mem.split(abi_text, "."); + var abi_it = mem.split(u8, abi_text, "."); const abi = std.meta.stringToEnum(Target.Abi, abi_it.next().?) orelse return error.UnknownApplicationBinaryInterface; result.abi = abi; @@ -699,7 +699,7 @@ pub const CrossTarget = struct { } fn parseOs(result: *CrossTarget, diags: *ParseOptions.Diagnostics, text: []const u8) !void { - var it = mem.split(text, "."); + var it = mem.split(u8, text, "."); const os_name = it.next().?; diags.os_name = os_name; const os_is_native = mem.eql(u8, os_name, "native"); @@ -757,7 +757,7 @@ pub const CrossTarget = struct { .linux, .dragonfly, => { - var range_it = mem.split(version_text, "..."); + var range_it = mem.split(u8, version_text, "..."); const min_text = range_it.next().?; const min_ver = SemVer.parse(min_text) catch |err| switch (err) { @@ -777,7 +777,7 @@ pub const CrossTarget = struct { }, .windows => { - var range_it = mem.split(version_text, "..."); + var range_it = mem.split(u8, version_text, "..."); const min_text = range_it.next().?; const min_ver = std.meta.stringToEnum(Target.Os.WindowsVersion, min_text) orelse diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index a4939b8347..4d671efe94 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -44,7 +44,7 @@ pub const NativePaths = struct { defer allocator.free(nix_cflags_compile); is_nix = true; - var it = mem.tokenize(nix_cflags_compile, " "); + var it = mem.tokenize(u8, nix_cflags_compile, " "); while (true) { const word = it.next() orelse break; if (mem.eql(u8, word, "-isystem")) { @@ -69,7 +69,7 @@ pub const NativePaths = struct { defer allocator.free(nix_ldflags); is_nix = true; - var it = mem.tokenize(nix_ldflags, " "); + var it = mem.tokenize(u8, nix_ldflags, " "); while (true) { const word = it.next() orelse break; if (mem.eql(u8, word, "-rpath")) { @@ -839,7 +839,7 @@ pub const NativeTargetInfo = struct { error.Overflow => return error.InvalidElfFile, }; const rpath_list = mem.spanZ(std.meta.assumeSentinel(strtab[rpoff_usize..].ptr, 0)); - var it = mem.tokenize(rpath_list, ":"); + var it = mem.tokenize(u8, rpath_list, ":"); while (it.next()) |rpath| { var dir = fs.cwd().openDir(rpath, .{}) catch |err| switch (err) { error.NameTooLong => unreachable, diff --git a/src/Cache.zig b/src/Cache.zig index 94c1b41c61..28401c3d18 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -356,7 +356,7 @@ pub const Manifest = struct { const input_file_count = self.files.items.len; var any_file_changed = false; - var line_iter = mem.tokenize(file_contents, "\n"); + var line_iter = mem.tokenize(u8, file_contents, "\n"); var idx: usize = 0; while (line_iter.next()) |line| { defer idx += 1; @@ -373,7 +373,7 @@ pub const Manifest = struct { break :blk new; }; - var iter = mem.tokenize(line, " "); + var iter = mem.tokenize(u8, line, " "); const size = iter.next() orelse return error.InvalidFormat; const inode = iter.next() orelse return error.InvalidFormat; const mtime_nsec_str = iter.next() orelse return error.InvalidFormat; diff --git a/src/Compilation.zig b/src/Compilation.zig index 4e9d94c6a4..a80849b26e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3341,7 +3341,7 @@ pub fn hasSharedLibraryExt(filename: []const u8) bool { return true; } // Look for .so.X, .so.X.Y, .so.X.Y.Z - var it = mem.split(filename, "."); + var it = mem.split(u8, filename, "."); _ = it.next().?; var so_txt = it.next() orelse return false; while (!mem.eql(u8, so_txt, "so")) { @@ -4086,7 +4086,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }; if (directory.handle.readFileAlloc(comp.gpa, libs_txt_basename, 10 * 1024 * 1024)) |libs_txt| { - var it = mem.tokenize(libs_txt, "\n"); + var it = mem.tokenize(u8, libs_txt, "\n"); while (it.next()) |lib_name| { try comp.stage1AddLinkLib(lib_name); } diff --git a/src/codegen.zig b/src/codegen.zig index 3864479d2d..7fd51d3cd8 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -3656,7 +3656,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } { - var iter = std.mem.tokenize(asm_source, "\n\r"); + var iter = std.mem.tokenize(u8, asm_source, "\n\r"); while (iter.next()) |ins| { if (mem.eql(u8, ins, "syscall")) { try self.code.appendSlice(&[_]u8{ 0x0f, 0x05 }); diff --git a/src/glibc.zig b/src/glibc.zig index 1a6756e467..aed1a24437 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -107,7 +107,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! defer gpa.free(abi_txt_contents); { - var it = mem.tokenize(vers_txt_contents, "\r\n"); + var it = mem.tokenize(u8, vers_txt_contents, "\r\n"); var line_i: usize = 1; while (it.next()) |line| : (line_i += 1) { const prefix = "GLIBC_"; @@ -124,10 +124,10 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! } } { - var file_it = mem.tokenize(fns_txt_contents, "\r\n"); + var file_it = mem.tokenize(u8, fns_txt_contents, "\r\n"); var line_i: usize = 1; while (file_it.next()) |line| : (line_i += 1) { - var line_it = mem.tokenize(line, " "); + var line_it = mem.tokenize(u8, line, " "); const fn_name = line_it.next() orelse { std.log.err("fns.txt:{d}: expected function name", .{line_i}); return error.ZigInstallationCorrupt; @@ -147,7 +147,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! } } { - var file_it = mem.split(abi_txt_contents, "\n"); + var file_it = mem.split(u8, abi_txt_contents, "\n"); var line_i: usize = 0; while (true) { const ver_list_base: []VerList = blk: { @@ -155,9 +155,9 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! if (line.len == 0) break; line_i += 1; const ver_list_base = try arena.alloc(VerList, all_functions.items.len); - var line_it = mem.tokenize(line, " "); + var line_it = mem.tokenize(u8, line, " "); while (line_it.next()) |target_string| { - var component_it = mem.tokenize(target_string, "-"); + var component_it = mem.tokenize(u8, target_string, "-"); const arch_name = component_it.next() orelse { std.log.err("abi.txt:{d}: expected arch name", .{line_i}); return error.ZigInstallationCorrupt; @@ -203,7 +203,7 @@ pub fn loadMetaData(gpa: *Allocator, zig_lib_dir: std.fs.Dir) LoadMetaDataError! .versions = undefined, .len = 0, }; - var line_it = mem.tokenize(line, " "); + var line_it = mem.tokenize(u8, line, " "); while (line_it.next()) |version_index_string| { if (ver_list.len >= ver_list.versions.len) { // If this happens with legit data, increase the array len in the type. diff --git a/src/libc_installation.zig b/src/libc_installation.zig index 783f76b9bd..b639e0f2f8 100644 --- a/src/libc_installation.zig +++ b/src/libc_installation.zig @@ -60,10 +60,10 @@ pub const LibCInstallation = struct { const contents = try std.fs.cwd().readFileAlloc(allocator, libc_file, std.math.maxInt(usize)); defer allocator.free(contents); - var it = std.mem.tokenize(contents, "\n"); + var it = std.mem.tokenize(u8, contents, "\n"); while (it.next()) |line| { if (line.len == 0 or line[0] == '#') continue; - var line_it = std.mem.split(line, "="); + var line_it = std.mem.split(u8, line, "="); const name = line_it.next() orelse { log.err("missing equal sign after field name\n", .{}); return error.ParseError; @@ -298,7 +298,7 @@ pub const LibCInstallation = struct { }, } - var it = std.mem.tokenize(exec_res.stderr, "\n\r"); + var it = std.mem.tokenize(u8, exec_res.stderr, "\n\r"); var search_paths = std.ArrayList([]const u8).init(allocator); defer search_paths.deinit(); while (it.next()) |line| { @@ -616,7 +616,7 @@ fn ccPrintFileName(args: CCPrintFileNameOptions) ![:0]u8 { }, } - var it = std.mem.tokenize(exec_res.stdout, "\n\r"); + var it = std.mem.tokenize(u8, exec_res.stdout, "\n\r"); const line = it.next() orelse return error.LibCRuntimeNotFound; // When this command fails, it returns exit code 0 and duplicates the input file name. // So we detect failure by checking if the output matches exactly the input. @@ -695,7 +695,7 @@ fn appendCcExe(args: *std.ArrayList([]const u8), skip_cc_env_var: bool) !void { return; }; // Respect space-separated flags to the C compiler. - var it = std.mem.tokenize(cc_env_var, " "); + var it = std.mem.tokenize(u8, cc_env_var, " "); while (it.next()) |arg| { try args.append(arg); } diff --git a/src/link/MachO/Dylib.zig b/src/link/MachO/Dylib.zig index 1d2833c764..4763203c3b 100644 --- a/src/link/MachO/Dylib.zig +++ b/src/link/MachO/Dylib.zig @@ -106,7 +106,7 @@ pub const Id = struct { var out: u32 = 0; var values: [3][]const u8 = undefined; - var split = mem.split(string, "."); + var split = mem.split(u8, string, "."); var count: u4 = 0; while (split.next()) |value| { if (count > 2) { diff --git a/src/main.zig b/src/main.zig index 8c1964fc43..0987a46989 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1200,7 +1200,7 @@ fn buildOutputType( }, .rdynamic => rdynamic = true, .wl => { - var split_it = mem.split(it.only_arg, ","); + var split_it = mem.split(u8, it.only_arg, ","); while (split_it.next()) |linker_arg| { // Handle nested-joined args like `-Wl,-rpath=foo`. // Must be prefixed with 1 or 2 dashes. @@ -3655,7 +3655,7 @@ pub const ClangArgIterator = struct { defer allocator.free(resp_contents); // TODO is there a specification for this file format? Let's find it and make this parsing more robust // at the very least I'm guessing this needs to handle quotes and `#` comments. - var it = mem.tokenize(resp_contents, " \t\r\n"); + var it = mem.tokenize(u8, resp_contents, " \t\r\n"); var resp_arg_list = std.ArrayList([]const u8).init(allocator); defer resp_arg_list.deinit(); { diff --git a/src/test.zig b/src/test.zig index af7b3a1736..960aac7bc7 100644 --- a/src/test.zig +++ b/src/test.zig @@ -228,7 +228,7 @@ pub const TestContext = struct { continue; } // example: "file.zig:1:2: error: bad thing happened" - var it = std.mem.split(err_msg_line, ":"); + var it = std.mem.split(u8, err_msg_line, ":"); const src_path = it.next() orelse @panic("missing colon"); const line_text = it.next() orelse @panic("missing line"); const col_text = it.next() orelse @panic("missing column"); @@ -779,7 +779,7 @@ pub const TestContext = struct { } var ok = true; if (case.expect_exact) { - var err_iter = std.mem.split(result.stderr, "\n"); + var err_iter = std.mem.split(u8, result.stderr, "\n"); var i: usize = 0; ok = while (err_iter.next()) |line| : (i += 1) { if (i >= case_error_list.len) break false; diff --git a/test/behavior/bugs/6456.zig b/test/behavior/bugs/6456.zig index 8078ab147f..44fdfd69ba 100644 --- a/test/behavior/bugs/6456.zig +++ b/test/behavior/bugs/6456.zig @@ -13,7 +13,7 @@ test "issue 6456" { comptime { var fields: []const StructField = &[0]StructField{}; - var it = std.mem.tokenize(text, "\n"); + var it = std.mem.tokenize(u8, text, "\n"); while (it.next()) |name| { fields = fields ++ &[_]StructField{StructField{ .alignment = 0, diff --git a/test/tests.zig b/test/tests.zig index fc83137bc4..89a2492386 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -768,7 +768,7 @@ pub const StackTracesContext = struct { var buf = ArrayList(u8).init(b.allocator); defer buf.deinit(); if (stderr.len != 0 and stderr[stderr.len - 1] == '\n') stderr = stderr[0 .. stderr.len - 1]; - var it = mem.split(stderr, "\n"); + var it = mem.split(u8, stderr, "\n"); process_lines: while (it.next()) |line| { if (line.len == 0) continue; diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index ba12f6e0e6..0b25d97572 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -188,9 +188,9 @@ pub fn main() !void { std.debug.warn("unable to open {s}: {}\n", .{ abi_list_filename, err }); std.process.exit(1); }; - var lines_it = std.mem.tokenize(contents, "\n"); + var lines_it = std.mem.tokenize(u8, contents, "\n"); while (lines_it.next()) |line| { - var tok_it = std.mem.tokenize(line, " "); + var tok_it = std.mem.tokenize(u8, line, " "); const ver = tok_it.next().?; const name = tok_it.next().?; const category = tok_it.next().?; @@ -319,8 +319,8 @@ pub fn strCmpLessThan(context: void, a: []const u8, b: []const u8) bool { pub fn versionLessThan(context: void, a: []const u8, b: []const u8) bool { _ = context; const sep_chars = "GLIBC_."; - var a_tokens = std.mem.tokenize(a, sep_chars); - var b_tokens = std.mem.tokenize(b, sep_chars); + var a_tokens = std.mem.tokenize(u8, a, sep_chars); + var b_tokens = std.mem.tokenize(u8, b, sep_chars); while (true) { const a_next = a_tokens.next(); diff --git a/tools/update_spirv_features.zig b/tools/update_spirv_features.zig index 0de1c56934..69e8237f98 100644 --- a/tools/update_spirv_features.zig +++ b/tools/update_spirv_features.zig @@ -19,7 +19,7 @@ const Version = struct { minor: u32, fn parse(str: []const u8) !Version { - var it = std.mem.split(str, "."); + var it = std.mem.split(u8, str, "."); const major = it.next() orelse return error.InvalidVersion; const minor = it.next() orelse return error.InvalidVersion; -- cgit v1.2.3 From f81b2531cb4904064446f84a06f6e09e4120e28a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 Aug 2021 15:41:52 -0700 Subject: stage2: pass some pointer tests * New AIR instructions: ptr_add, ptr_sub, ptr_elem_val, ptr_ptr_elem_val - See the doc comments for details. * Sema: implement runtime pointer arithmetic. * Sema: implement elem_val for many-pointers. * Sema: support coercion from `*[N:s]T` to `[*]T`. * Type: isIndexable handles many-pointers. --- src/Air.zig | 33 ++++- src/Liveness.zig | 4 + src/Sema.zig | 148 +++++++++++++----- src/codegen.zig | 40 +++-- src/codegen/c.zig | 28 +++- src/codegen/llvm.zig | 60 +++++++- src/codegen/llvm/bindings.zig | 3 + src/print_air.zig | 4 + src/type.zig | 14 +- test/behavior.zig | 3 +- test/behavior/pointers.zig | 301 ------------------------------------- test/behavior/pointers_stage1.zig | 305 ++++++++++++++++++++++++++++++++++++++ 12 files changed, 577 insertions(+), 366 deletions(-) create mode 100644 test/behavior/pointers_stage1.zig (limited to 'src/codegen.zig') diff --git a/src/Air.zig b/src/Air.zig index d923bf0b02..391683afd5 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -69,6 +69,18 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. div, + /// Add an offset to a pointer, returning a new pointer. + /// The offset is in element type units, not bytes. + /// Wrapping is undefined behavior. + /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs. + /// Uses the `bin_op` field. + ptr_add, + /// Subtract an offset from a pointer, returning a new pointer. + /// The offset is in element type units, not bytes. + /// Wrapping is undefined behavior. + /// The lhs is the pointer, rhs is the offset. Result type is the same as lhs. + /// Uses the `bin_op` field. + ptr_sub, /// Allocates stack local memory. /// Uses the `ty` field. alloc, @@ -264,6 +276,15 @@ pub const Inst = struct { /// Result type is the element type of the slice operand (2 element type operations). /// Uses the `bin_op` field. ptr_slice_elem_val, + /// Given a pointer value, and element index, return the element value at that index. + /// Result type is the element type of the pointer operand. + /// Uses the `bin_op` field. + ptr_elem_val, + /// Given a pointer to a pointer, and element index, return the element value of the inner + /// pointer at that index. + /// Result type is the element type of the inner pointer operand. + /// Uses the `bin_op` field. + ptr_ptr_elem_val, pub fn fromCmpOp(op: std.math.CompareOperator) Tag { return switch (op) { @@ -422,6 +443,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .bit_and, .bit_or, .xor, + .ptr_add, + .ptr_sub, => return air.typeOf(datas[inst].bin_op.lhs), .cmp_lt, @@ -495,14 +518,14 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { return callee_ty.fnReturnType(); }, - .slice_elem_val => { + .slice_elem_val, .ptr_elem_val => { const slice_ty = air.typeOf(datas[inst].bin_op.lhs); return slice_ty.elemType(); }, - .ptr_slice_elem_val => { - const ptr_slice_ty = air.typeOf(datas[inst].bin_op.lhs); - const slice_ty = ptr_slice_ty.elemType(); - return slice_ty.elemType(); + .ptr_slice_elem_val, .ptr_ptr_elem_val => { + const outer_ptr_ty = air.typeOf(datas[inst].bin_op.lhs); + const inner_ptr_ty = outer_ptr_ty.elemType(); + return inner_ptr_ty.elemType(); }, } } diff --git a/src/Liveness.zig b/src/Liveness.zig index 4e22febc5a..48603fc7c9 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -231,6 +231,8 @@ fn analyzeInst( .mul, .mulwrap, .div, + .ptr_add, + .ptr_sub, .bit_and, .bit_or, .xor, @@ -245,6 +247,8 @@ fn analyzeInst( .store, .slice_elem_val, .ptr_slice_elem_val, + .ptr_elem_val, + .ptr_ptr_elem_val, => { const o = inst_datas[inst].bin_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index 6c68ceaf2a..a783a48c64 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -5471,6 +5471,41 @@ fn analyzeArithmetic( lhs_ty, rhs_ty, }); } + if (lhs_zig_ty_tag == .Pointer) switch (lhs_ty.ptrSize()) { + .One, .Slice => {}, + .Many, .C => { + // Pointer arithmetic. + const op_src = src; // TODO better source location + const air_tag: Air.Inst.Tag = switch (zir_tag) { + .add => .ptr_add, + .sub => .ptr_sub, + else => return sema.mod.fail( + &block.base, + op_src, + "invalid pointer arithmetic operand: '{s}''", + .{@tagName(zir_tag)}, + ), + }; + // TODO if the operand is comptime-known to be negative, or is a negative int, + // coerce to isize instead of usize. + const casted_rhs = try sema.coerce(block, Type.initTag(.usize), rhs, rhs_src); + const runtime_src = runtime_src: { + if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveDefinedValue(block, rhs_src, casted_rhs)) |rhs_val| { + _ = lhs_val; + _ = rhs_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for comptime pointer arithmetic", .{}); + } else { + break :runtime_src rhs_src; + } + } else { + break :runtime_src lhs_src; + } + }; + try sema.requireRuntimeBlock(block, runtime_src); + return block.addBinOp(air_tag, lhs, casted_rhs); + }, + }; const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; const resolved_type = try sema.resolvePeerTypes(block, src, instructions); @@ -7959,38 +7994,83 @@ fn elemVal( ) CompileError!Air.Inst.Ref { const array_ptr_src = src; // TODO better source location const maybe_ptr_ty = sema.typeOf(array_maybe_ptr); - if (maybe_ptr_ty.isSinglePointer()) { - const indexable_ty = maybe_ptr_ty.elemType(); - if (indexable_ty.isSlice()) { - // We have a pointer to a slice and we want an element value. - if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { - const slice = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); - if (try sema.resolveDefinedValue(block, src, slice)) |slice_val| { + switch (maybe_ptr_ty.zigTypeTag()) { + .Pointer => switch (maybe_ptr_ty.ptrSize()) { + .Slice => { + if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |slice_val| { _ = slice_val; return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); } try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.slice_elem_val, slice, elem_index); - } - try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.ptr_slice_elem_val, array_maybe_ptr, elem_index); - } - } - if (maybe_ptr_ty.isSlice()) { - if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |slice_val| { - _ = slice_val; - return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); - } - try sema.requireRuntimeBlock(block, src); - return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index); + return block.addBinOp(.slice_elem_val, array_maybe_ptr, elem_index); + }, + .Many, .C => { + if (try sema.resolveDefinedValue(block, src, array_maybe_ptr)) |ptr_val| { + _ = ptr_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_elem_val, array_maybe_ptr, elem_index); + }, + .One => { + const indexable_ty = maybe_ptr_ty.elemType(); + switch (indexable_ty.zigTypeTag()) { + .Pointer => switch (indexable_ty.ptrSize()) { + .Slice => { + // We have a pointer to a slice and we want an element value. + if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { + const slice = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); + if (try sema.resolveDefinedValue(block, src, slice)) |slice_val| { + _ = slice_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known slice", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.slice_elem_val, slice, elem_index); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_slice_elem_val, array_maybe_ptr, elem_index); + }, + .Many, .C => { + // We have a pointer to a pointer and we want an element value. + if (try sema.isComptimeKnown(block, src, array_maybe_ptr)) { + const ptr = try sema.analyzeLoad(block, src, array_maybe_ptr, array_ptr_src); + if (try sema.resolveDefinedValue(block, src, ptr)) |ptr_val| { + _ = ptr_val; + return sema.mod.fail(&block.base, src, "TODO implement Sema for elemVal for comptime known pointer", .{}); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_elem_val, ptr, elem_index); + } + try sema.requireRuntimeBlock(block, src); + return block.addBinOp(.ptr_ptr_elem_val, array_maybe_ptr, elem_index); + }, + .One => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{indexable_ty.elemType()}, + ), + }, + .Array => { + const ptr = try sema.elemPtr(block, src, array_maybe_ptr, elem_index, elem_index_src); + return sema.analyzeLoad(block, src, ptr, elem_index_src); + }, + else => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{indexable_ty}, + ), + } + }, + }, + else => return sema.mod.fail( + &block.base, + array_ptr_src, + "expected pointer, found '{}'", + .{maybe_ptr_ty}, + ), } - - const array_ptr = if (maybe_ptr_ty.zigTypeTag() == .Pointer) - array_maybe_ptr - else - try sema.analyzeRef(block, src, array_maybe_ptr); - const ptr = try sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src); - return sema.analyzeLoad(block, src, ptr, elem_index_src); } fn elemPtrArray( @@ -8107,17 +8187,15 @@ fn coerce( .Many => { // *[N]T to [*]T // *[N:s]T to [*:s]T - const src_sentinel = array_type.sentinel(); - const dst_sentinel = dest_type.sentinel(); - if (src_sentinel == null and dst_sentinel == null) - return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); - - if (src_sentinel) |src_s| { - if (dst_sentinel) |dst_s| { - if (src_s.eql(dst_s, dst_elem_type)) { + // *[N:s]T to [*]T + if (dest_type.sentinel()) |dst_sentinel| { + if (array_type.sentinel()) |src_sentinel| { + if (src_sentinel.eql(dst_sentinel, dst_elem_type)) { return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } } + } else { + return sema.coerceArrayPtrToMany(block, dest_type, inst, inst_src); } }, .One => {}, diff --git a/src/codegen.zig b/src/codegen.zig index 7fd51d3cd8..f5cdc518f6 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -802,13 +802,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { switch (air_tags[inst]) { // zig fmt: off - .add => try self.airAdd(inst), - .addwrap => try self.airAddWrap(inst), - .sub => try self.airSub(inst), - .subwrap => try self.airSubWrap(inst), - .mul => try self.airMul(inst), - .mulwrap => try self.airMulWrap(inst), - .div => try self.airDiv(inst), + .add, .ptr_add => try self.airAdd(inst), + .addwrap => try self.airAddWrap(inst), + .sub, .ptr_sub => try self.airSub(inst), + .subwrap => try self.airSubWrap(inst), + .mul => try self.airMul(inst), + .mulwrap => try self.airMulWrap(inst), + .div => try self.airDiv(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -859,6 +859,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), + .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .constant => unreachable, // excluded from function bodies .const_ty => unreachable, // excluded from function bodies @@ -1369,21 +1371,41 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { } fn airSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { else => return self.fail("TODO implement slice_elem_val for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } fn airPtrSliceElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO const bin_op = self.air.instructions.items(.data)[inst].bin_op; - const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { else => return self.fail("TODO implement ptr_slice_elem_val for {}", .{self.target.cpu.arch}), }; return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_elem_val for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + + fn airPtrPtrElemVal(self: *Self, inst: Air.Inst.Index) !void { + const is_volatile = false; // TODO + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (!is_volatile and self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement ptr_ptr_elem_val for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool { if (!self.liveness.operandDies(inst, op_index)) return false; diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 22420aca45..65ad4bac8e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -850,19 +850,19 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. - .add => try airBinOp( o, inst, " + "), - .addwrap => try airWrapOp(o, inst, " + ", "addw_"), + .add, .ptr_add => try airBinOp( o, inst, " + "), + .addwrap => try airWrapOp(o, inst, " + ", "addw_"), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. - .sub => try airBinOp( o, inst, " - "), - .subwrap => try airWrapOp(o, inst, " - ", "subw_"), + .sub, .ptr_sub => try airBinOp( o, inst, " - "), + .subwrap => try airWrapOp(o, inst, " - ", "subw_"), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. - .mul => try airBinOp( o, inst, " * "), - .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"), + .mul => try airBinOp( o, inst, " * "), + .mulwrap => try airWrapOp(o, inst, " * ", "mulw_"), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. - .div => try airBinOp( o, inst, " / "), + .div => try airBinOp( o, inst, " / "), .cmp_eq => try airBinOp(o, inst, " == "), .cmp_gt => try airBinOp(o, inst, " > "), @@ -915,6 +915,8 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM .slice_ptr => try airSliceField(o, inst, ".ptr;\n"), .slice_len => try airSliceField(o, inst, ".len;\n"), + .ptr_elem_val => try airPtrElemVal(o, inst, "["), + .ptr_ptr_elem_val => try airPtrElemVal(o, inst, "[0]["), .slice_elem_val => try airSliceElemVal(o, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["), @@ -953,8 +955,18 @@ fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue { return local; } +fn airPtrElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { + const is_volatile = false; // TODO + if (!is_volatile and o.liveness.isUnused(inst)) + return CValue.none; + + _ = prefix; + return o.dg.fail("TODO: C backend: airPtrElemVal", .{}); +} + fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue { - if (o.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and o.liveness.isUnused(inst)) return CValue.none; const bin_op = o.air.instructions.items(.data)[inst].bin_op; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4b7454d878..eccd5fa04f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -966,6 +966,8 @@ pub const FuncGen = struct { .mul => try self.airMul(inst, false), .mulwrap => try self.airMul(inst, true), .div => try self.airDiv(inst), + .ptr_add => try self.airPtrAdd(inst), + .ptr_sub => try self.airPtrSub(inst), .bit_and, .bool_and => try self.airAnd(inst), .bit_or, .bool_or => try self.airOr(inst), @@ -1015,6 +1017,8 @@ pub const FuncGen = struct { .slice_elem_val => try self.airSliceElemVal(inst), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst), + .ptr_elem_val => try self.airPtrElemVal(inst), + .ptr_ptr_elem_val => try self.airPtrPtrElemVal(inst), .optional_payload => try self.airOptionalPayload(inst, false), .optional_payload_ptr => try self.airOptionalPayload(inst, true), @@ -1229,7 +1233,8 @@ pub const FuncGen = struct { } fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -1242,7 +1247,8 @@ pub const FuncGen = struct { } fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { - if (self.liveness.isUnused(inst)) + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) return null; const bin_op = self.air.instructions.items(.data)[inst].bin_op; @@ -1264,6 +1270,33 @@ pub const FuncGen = struct { return self.builder.buildLoad(ptr, ""); } + fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + + fn airPtrPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + const is_volatile = false; // TODO + if (!is_volatile and self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const lhs = try self.resolveInst(bin_op.lhs); + const rhs = try self.resolveInst(bin_op.rhs); + const base_ptr = self.builder.buildLoad(lhs, ""); + const indices: [1]*const llvm.Value = .{rhs}; + const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + return self.builder.buildLoad(ptr, ""); + } + fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; @@ -1624,6 +1657,29 @@ pub const FuncGen = struct { return self.builder.buildUDiv(lhs, rhs, ""); } + fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const offset = try self.resolveInst(bin_op.rhs); + const indices: [1]*const llvm.Value = .{offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + + fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) + return null; + + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const base_ptr = try self.resolveInst(bin_op.lhs); + const offset = try self.resolveInst(bin_op.rhs); + const negative_offset = self.builder.buildNeg(offset, ""); + const indices: [1]*const llvm.Value = .{negative_offset}; + return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); + } + fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 096cd4a5b4..4bb8a4a18b 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -324,6 +324,9 @@ pub const Builder = opaque { pub const buildLoad = LLVMBuildLoad; extern fn LLVMBuildLoad(*const Builder, PointerVal: *const Value, Name: [*:0]const u8) *const Value; + pub const buildNeg = LLVMBuildNeg; + extern fn LLVMBuildNeg(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value; + pub const buildNot = LLVMBuildNot; extern fn LLVMBuildNot(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value; diff --git a/src/print_air.zig b/src/print_air.zig index 11f2982fc3..66490b6512 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -109,6 +109,8 @@ const Writer = struct { .mul, .mulwrap, .div, + .ptr_add, + .ptr_sub, .bit_and, .bit_or, .xor, @@ -123,6 +125,8 @@ const Writer = struct { .store, .slice_elem_val, .ptr_slice_elem_val, + .ptr_elem_val, + .ptr_ptr_elem_val, => try w.writeBinOp(s, inst), .is_null, diff --git a/src/type.zig b/src/type.zig index 02b9fabe71..28b87a8afe 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2753,11 +2753,15 @@ pub const Type = extern union { }; } - pub fn isIndexable(self: Type) bool { - const zig_tag = self.zigTypeTag(); - // TODO tuples are indexable - return zig_tag == .Array or zig_tag == .Vector or self.isSlice() or - (self.isSinglePointer() and self.elemType().zigTypeTag() == .Array); + pub fn isIndexable(ty: Type) bool { + return switch (ty.zigTypeTag()) { + .Array, .Vector => true, + .Pointer => switch (ty.ptrSize()) { + .Slice, .Many, .C => true, + .One => ty.elemType().zigTypeTag() == .Array, + }, + else => false, // TODO tuples are indexable + }; } /// Returns null if the type has no namespace. diff --git a/test/behavior.zig b/test/behavior.zig index f1c6fa3a35..936268af9c 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -6,6 +6,7 @@ test { _ = @import("behavior/basic.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/eval.zig"); + _ = @import("behavior/pointers.zig"); if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. @@ -112,7 +113,7 @@ test { _ = @import("behavior/namespace_depends_on_compile_var.zig"); _ = @import("behavior/null.zig"); _ = @import("behavior/optional.zig"); - _ = @import("behavior/pointers.zig"); + _ = @import("behavior/pointers_stage1.zig"); _ = @import("behavior/popcount.zig"); _ = @import("behavior/ptrcast.zig"); _ = @import("behavior/pub_enum.zig"); diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index bb95d3c219..4fcd78b1d6 100644 --- a/test/behavior/pointers.zig +++ b/test/behavior/pointers.zig @@ -15,22 +15,6 @@ fn testDerefPtr() !void { try expect(x == 1235); } -const Foo1 = struct { - x: void, -}; - -test "dereference pointer again" { - try testDerefPtrOneVal(); - comptime try testDerefPtrOneVal(); -} - -fn testDerefPtrOneVal() !void { - // Foo1 satisfies the OnePossibleValueYes criteria - const x = &Foo1{ .x = {} }; - const y = x.*; - try expect(@TypeOf(y.x) == void); -} - test "pointer arithmetic" { var ptr: [*]const u8 = "abcd"; @@ -60,288 +44,3 @@ test "double pointer parsing" { fn PtrOf(comptime T: type) type { return *T; } - -test "assigning integer to C pointer" { - var x: i32 = 0; - var ptr: [*c]u8 = 0; - var ptr2: [*c]u8 = x; - if (false) { - ptr; - ptr2; - } -} - -test "implicit cast single item pointer to C pointer and back" { - var y: u8 = 11; - var x: [*c]u8 = &y; - var z: *u8 = x; - z.* += 1; - try expect(y == 12); -} - -test "C pointer comparison and arithmetic" { - const S = struct { - fn doTheTest() !void { - var ptr1: [*c]u32 = 0; - var ptr2 = ptr1 + 10; - try expect(ptr1 == 0); - try expect(ptr1 >= 0); - try expect(ptr1 <= 0); - // expect(ptr1 < 1); - // expect(ptr1 < one); - // expect(1 > ptr1); - // expect(one > ptr1); - try expect(ptr1 < ptr2); - try expect(ptr2 > ptr1); - try expect(ptr2 >= 40); - try expect(ptr2 == 40); - try expect(ptr2 <= 40); - ptr2 -= 10; - try expect(ptr1 == ptr2); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "peer type resolution with C pointers" { - var ptr_one: *u8 = undefined; - var ptr_many: [*]u8 = undefined; - var ptr_c: [*c]u8 = undefined; - var t = true; - var x1 = if (t) ptr_one else ptr_c; - var x2 = if (t) ptr_many else ptr_c; - var x3 = if (t) ptr_c else ptr_one; - var x4 = if (t) ptr_c else ptr_many; - try expect(@TypeOf(x1) == [*c]u8); - try expect(@TypeOf(x2) == [*c]u8); - try expect(@TypeOf(x3) == [*c]u8); - try expect(@TypeOf(x4) == [*c]u8); -} - -test "implicit casting between C pointer and optional non-C pointer" { - var slice: []const u8 = "aoeu"; - const opt_many_ptr: ?[*]const u8 = slice.ptr; - var ptr_opt_many_ptr = &opt_many_ptr; - var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr; - try expect(c_ptr.*.* == 'a'); - ptr_opt_many_ptr = c_ptr; - try expect(ptr_opt_many_ptr.*.?[1] == 'o'); -} - -test "implicit cast error unions with non-optional to optional pointer" { - const S = struct { - fn doTheTest() !void { - try expectError(error.Fail, foo()); - } - fn foo() anyerror!?*u8 { - return bar() orelse error.Fail; - } - fn bar() ?*u8 { - return null; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "initialize const optional C pointer to null" { - const a: ?[*c]i32 = null; - try expect(a == null); - comptime try expect(a == null); -} - -test "compare equality of optional and non-optional pointer" { - const a = @intToPtr(*const usize, 0x12345678); - const b = @intToPtr(?*usize, 0x12345678); - try expect(a == b); - try expect(b == a); -} - -test "allowzero pointer and slice" { - var ptr = @intToPtr([*]allowzero i32, 0); - var opt_ptr: ?[*]allowzero i32 = ptr; - try expect(opt_ptr != null); - try expect(@ptrToInt(ptr) == 0); - var runtime_zero: usize = 0; - var slice = ptr[runtime_zero..10]; - comptime try expect(@TypeOf(slice) == []allowzero i32); - try expect(@ptrToInt(&slice[5]) == 20); - - comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero); - comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero); -} - -test "assign null directly to C pointer and test null equality" { - var x: [*c]i32 = null; - try expect(x == null); - try expect(null == x); - try expect(!(x != null)); - try expect(!(null != x)); - if (x) |same_x| { - _ = same_x; - @panic("fail"); - } - var otherx: i32 = undefined; - try expect((x orelse &otherx) == &otherx); - - const y: [*c]i32 = null; - comptime try expect(y == null); - comptime try expect(null == y); - comptime try expect(!(y != null)); - comptime try expect(!(null != y)); - if (y) |same_y| { - _ = same_y; - @panic("fail"); - } - const othery: i32 = undefined; - comptime try expect((y orelse &othery) == &othery); - - var n: i32 = 1234; - var x1: [*c]i32 = &n; - try expect(!(x1 == null)); - try expect(!(null == x1)); - try expect(x1 != null); - try expect(null != x1); - try expect(x1.?.* == 1234); - if (x1) |same_x1| { - try expect(same_x1.* == 1234); - } else { - @panic("fail"); - } - try expect((x1 orelse &otherx) == x1); - - const nc: i32 = 1234; - const y1: [*c]const i32 = &nc; - comptime try expect(!(y1 == null)); - comptime try expect(!(null == y1)); - comptime try expect(y1 != null); - comptime try expect(null != y1); - comptime try expect(y1.?.* == 1234); - if (y1) |same_y1| { - try expect(same_y1.* == 1234); - } else { - @compileError("fail"); - } - comptime try expect((y1 orelse &othery) == y1); -} - -test "null terminated pointer" { - const S = struct { - fn doTheTest() !void { - var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' }; - var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero); - var no_zero_ptr: [*]const u8 = zero_ptr; - var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr); - try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello")); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "allow any sentinel" { - const S = struct { - fn doTheTest() !void { - var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 }; - var ptr: [*:std.math.minInt(i32)]i32 = &array; - try expect(ptr[4] == std.math.minInt(i32)); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with enums" { - const S = struct { - const Number = enum { - one, - two, - sentinel, - }; - - fn doTheTest() !void { - var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one }; - try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with optional element" { - const S = struct { - fn doTheTest() !void { - var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 }; - try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer sentinel with +inf" { - const S = struct { - fn doTheTest() !void { - const inf = std.math.inf_f32; - var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 }; - try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731 - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "pointer to array at fixed address" { - const array = @intToPtr(*volatile [1]u32, 0x10); - // Silly check just to reference `array` - try expect(@ptrToInt(&array[0]) == 0x10); -} - -test "pointer arithmetic affects the alignment" { - { - var ptr: [*]align(8) u32 = undefined; - var x: usize = 1; - - try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8); - const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4 - try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4); - const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8 - try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8); - const ptr3 = ptr + 0; // no-op - try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); - const ptr4 = ptr + x; // runtime-known addend - try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); - } - { - var ptr: [*]align(8) [3]u8 = undefined; - var x: usize = 1; - - const ptr1 = ptr + 17; // 3 * 17 = 51 - try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1); - const ptr2 = ptr + x; // runtime-known addend - try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1); - const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8 - try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); - const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4 - try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); - } -} - -test "@ptrToInt on null optional at comptime" { - { - const pointer = @intToPtr(?*u8, 0x000); - const x = @ptrToInt(pointer); - _ = x; - comptime try expect(0 == @ptrToInt(pointer)); - } - { - const pointer = @intToPtr(?*u8, 0xf00); - comptime try expect(0xf00 == @ptrToInt(pointer)); - } -} - -test "indexing array with sentinel returns correct type" { - var s: [:0]const u8 = "abc"; - try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0]))); -} diff --git a/test/behavior/pointers_stage1.zig b/test/behavior/pointers_stage1.zig new file mode 100644 index 0000000000..aea123a5c3 --- /dev/null +++ b/test/behavior/pointers_stage1.zig @@ -0,0 +1,305 @@ +const std = @import("std"); +const testing = std.testing; +const expect = testing.expect; +const expectError = testing.expectError; + +const Foo1 = struct { + x: void, +}; + +test "dereference pointer again" { + try testDerefPtrOneVal(); + comptime try testDerefPtrOneVal(); +} + +fn testDerefPtrOneVal() !void { + // Foo1 satisfies the OnePossibleValueYes criteria + const x = &Foo1{ .x = {} }; + const y = x.*; + try expect(@TypeOf(y.x) == void); +} + +test "assigning integer to C pointer" { + var x: i32 = 0; + var ptr: [*c]u8 = 0; + var ptr2: [*c]u8 = x; + if (false) { + ptr; + ptr2; + } +} + +test "implicit cast single item pointer to C pointer and back" { + var y: u8 = 11; + var x: [*c]u8 = &y; + var z: *u8 = x; + z.* += 1; + try expect(y == 12); +} + +test "C pointer comparison and arithmetic" { + const S = struct { + fn doTheTest() !void { + var ptr1: [*c]u32 = 0; + var ptr2 = ptr1 + 10; + try expect(ptr1 == 0); + try expect(ptr1 >= 0); + try expect(ptr1 <= 0); + // expect(ptr1 < 1); + // expect(ptr1 < one); + // expect(1 > ptr1); + // expect(one > ptr1); + try expect(ptr1 < ptr2); + try expect(ptr2 > ptr1); + try expect(ptr2 >= 40); + try expect(ptr2 == 40); + try expect(ptr2 <= 40); + ptr2 -= 10; + try expect(ptr1 == ptr2); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "peer type resolution with C pointers" { + var ptr_one: *u8 = undefined; + var ptr_many: [*]u8 = undefined; + var ptr_c: [*c]u8 = undefined; + var t = true; + var x1 = if (t) ptr_one else ptr_c; + var x2 = if (t) ptr_many else ptr_c; + var x3 = if (t) ptr_c else ptr_one; + var x4 = if (t) ptr_c else ptr_many; + try expect(@TypeOf(x1) == [*c]u8); + try expect(@TypeOf(x2) == [*c]u8); + try expect(@TypeOf(x3) == [*c]u8); + try expect(@TypeOf(x4) == [*c]u8); +} + +test "implicit casting between C pointer and optional non-C pointer" { + var slice: []const u8 = "aoeu"; + const opt_many_ptr: ?[*]const u8 = slice.ptr; + var ptr_opt_many_ptr = &opt_many_ptr; + var c_ptr: [*c]const [*c]const u8 = ptr_opt_many_ptr; + try expect(c_ptr.*.* == 'a'); + ptr_opt_many_ptr = c_ptr; + try expect(ptr_opt_many_ptr.*.?[1] == 'o'); +} + +test "implicit cast error unions with non-optional to optional pointer" { + const S = struct { + fn doTheTest() !void { + try expectError(error.Fail, foo()); + } + fn foo() anyerror!?*u8 { + return bar() orelse error.Fail; + } + fn bar() ?*u8 { + return null; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "initialize const optional C pointer to null" { + const a: ?[*c]i32 = null; + try expect(a == null); + comptime try expect(a == null); +} + +test "compare equality of optional and non-optional pointer" { + const a = @intToPtr(*const usize, 0x12345678); + const b = @intToPtr(?*usize, 0x12345678); + try expect(a == b); + try expect(b == a); +} + +test "allowzero pointer and slice" { + var ptr = @intToPtr([*]allowzero i32, 0); + var opt_ptr: ?[*]allowzero i32 = ptr; + try expect(opt_ptr != null); + try expect(@ptrToInt(ptr) == 0); + var runtime_zero: usize = 0; + var slice = ptr[runtime_zero..10]; + comptime try expect(@TypeOf(slice) == []allowzero i32); + try expect(@ptrToInt(&slice[5]) == 20); + + comptime try expect(@typeInfo(@TypeOf(ptr)).Pointer.is_allowzero); + comptime try expect(@typeInfo(@TypeOf(slice)).Pointer.is_allowzero); +} + +test "assign null directly to C pointer and test null equality" { + var x: [*c]i32 = null; + try expect(x == null); + try expect(null == x); + try expect(!(x != null)); + try expect(!(null != x)); + if (x) |same_x| { + _ = same_x; + @panic("fail"); + } + var otherx: i32 = undefined; + try expect((x orelse &otherx) == &otherx); + + const y: [*c]i32 = null; + comptime try expect(y == null); + comptime try expect(null == y); + comptime try expect(!(y != null)); + comptime try expect(!(null != y)); + if (y) |same_y| { + _ = same_y; + @panic("fail"); + } + const othery: i32 = undefined; + comptime try expect((y orelse &othery) == &othery); + + var n: i32 = 1234; + var x1: [*c]i32 = &n; + try expect(!(x1 == null)); + try expect(!(null == x1)); + try expect(x1 != null); + try expect(null != x1); + try expect(x1.?.* == 1234); + if (x1) |same_x1| { + try expect(same_x1.* == 1234); + } else { + @panic("fail"); + } + try expect((x1 orelse &otherx) == x1); + + const nc: i32 = 1234; + const y1: [*c]const i32 = &nc; + comptime try expect(!(y1 == null)); + comptime try expect(!(null == y1)); + comptime try expect(y1 != null); + comptime try expect(null != y1); + comptime try expect(y1.?.* == 1234); + if (y1) |same_y1| { + try expect(same_y1.* == 1234); + } else { + @compileError("fail"); + } + comptime try expect((y1 orelse &othery) == y1); +} + +test "null terminated pointer" { + const S = struct { + fn doTheTest() !void { + var array_with_zero = [_:0]u8{ 'h', 'e', 'l', 'l', 'o' }; + var zero_ptr: [*:0]const u8 = @ptrCast([*:0]const u8, &array_with_zero); + var no_zero_ptr: [*]const u8 = zero_ptr; + var zero_ptr_again = @ptrCast([*:0]const u8, no_zero_ptr); + try expect(std.mem.eql(u8, std.mem.spanZ(zero_ptr_again), "hello")); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "allow any sentinel" { + const S = struct { + fn doTheTest() !void { + var array = [_:std.math.minInt(i32)]i32{ 1, 2, 3, 4 }; + var ptr: [*:std.math.minInt(i32)]i32 = &array; + try expect(ptr[4] == std.math.minInt(i32)); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with enums" { + const S = struct { + const Number = enum { + one, + two, + sentinel, + }; + + fn doTheTest() !void { + var ptr: [*:.sentinel]const Number = &[_:.sentinel]Number{ .one, .two, .two, .one }; + try expect(ptr[4] == .sentinel); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with optional element" { + const S = struct { + fn doTheTest() !void { + var ptr: [*:null]const ?i32 = &[_:null]?i32{ 1, 2, 3, 4 }; + try expect(ptr[4] == null); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer sentinel with +inf" { + const S = struct { + fn doTheTest() !void { + const inf = std.math.inf_f32; + var ptr: [*:inf]const f32 = &[_:inf]f32{ 1.1, 2.2, 3.3, 4.4 }; + try expect(ptr[4] == inf); // TODO this should be comptime try expect, see #3731 + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "pointer to array at fixed address" { + const array = @intToPtr(*volatile [1]u32, 0x10); + // Silly check just to reference `array` + try expect(@ptrToInt(&array[0]) == 0x10); +} + +test "pointer arithmetic affects the alignment" { + { + var ptr: [*]align(8) u32 = undefined; + var x: usize = 1; + + try expect(@typeInfo(@TypeOf(ptr)).Pointer.alignment == 8); + const ptr1 = ptr + 1; // 1 * 4 = 4 -> lcd(4,8) = 4 + try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 4); + const ptr2 = ptr + 4; // 4 * 4 = 16 -> lcd(16,8) = 8 + try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 8); + const ptr3 = ptr + 0; // no-op + try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + x; // runtime-known addend + try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } + { + var ptr: [*]align(8) [3]u8 = undefined; + var x: usize = 1; + + const ptr1 = ptr + 17; // 3 * 17 = 51 + try expect(@typeInfo(@TypeOf(ptr1)).Pointer.alignment == 1); + const ptr2 = ptr + x; // runtime-known addend + try expect(@typeInfo(@TypeOf(ptr2)).Pointer.alignment == 1); + const ptr3 = ptr + 8; // 3 * 8 = 24 -> lcd(8,24) = 8 + try expect(@typeInfo(@TypeOf(ptr3)).Pointer.alignment == 8); + const ptr4 = ptr + 4; // 3 * 4 = 12 -> lcd(8,12) = 4 + try expect(@typeInfo(@TypeOf(ptr4)).Pointer.alignment == 4); + } +} + +test "@ptrToInt on null optional at comptime" { + { + const pointer = @intToPtr(?*u8, 0x000); + const x = @ptrToInt(pointer); + _ = x; + comptime try expect(0 == @ptrToInt(pointer)); + } + { + const pointer = @intToPtr(?*u8, 0xf00); + comptime try expect(0xf00 == @ptrToInt(pointer)); + } +} + +test "indexing array with sentinel returns correct type" { + var s: [:0]const u8 = "abc"; + try testing.expectEqualSlices(u8, "*const u8", @typeName(@TypeOf(&s[0]))); +} -- cgit v1.2.3 From 799fedf612aa8742c446b015c12d21707a1dbec0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 Aug 2021 20:34:28 -0700 Subject: stage2: pass some error union tests * Value: rename `error_union` to `eu_payload` and clarify the intended usage in the doc comments. The way error unions is represented with Value is fixed to not have ambiguous values. * Fix codegen for error union constants in all the backends. * Implement the AIR instructions having to do with error unions in the LLVM backend. --- src/Sema.zig | 16 ++++------ src/codegen.zig | 2 +- src/codegen/c.zig | 25 ++++++--------- src/codegen/llvm.zig | 78 +++++++++++++++++++++++++++++---------------- src/codegen/wasm.zig | 18 +++++------ src/value.zig | 43 ++++++++++++++++++------- test/behavior.zig | 3 +- test/behavior/if.zig | 42 ------------------------ test/behavior/if_stage1.zig | 45 ++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 117 deletions(-) create mode 100644 test/behavior/if_stage1.zig (limited to 'src/codegen.zig') diff --git a/src/Sema.zig b/src/Sema.zig index a783a48c64..96a09553f5 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3468,7 +3468,7 @@ fn zirErrUnionPayload( if (val.getError()) |name| { return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); } - const data = val.castTag(.error_union).?.data; + const data = val.castTag(.eu_payload).?.data; const result_ty = operand_ty.errorUnionPayload(); return sema.addConstant(result_ty, data); } @@ -3539,8 +3539,7 @@ fn zirErrUnionCode(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compi if (try sema.resolveDefinedValue(block, src, operand)) |val| { assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + return sema.addConstant(result_ty, val); } try sema.requireRuntimeBlock(block, src); @@ -3566,8 +3565,7 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try pointer_val.pointerDeref(sema.arena)) |val| { assert(val.getError() != null); - const data = val.castTag(.error_union).?.data; - return sema.addConstant(result_ty, data); + return sema.addConstant(result_ty, val); } } @@ -8900,7 +8898,9 @@ fn wrapErrorUnion( if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { if (inst_ty.zigTypeTag() != .ErrorSet) { _ = try sema.coerce(block, dest_payload_ty, inst, inst_src); - } else switch (dest_err_set_ty.tag()) { + return sema.addConstant(dest_type, try Value.Tag.eu_payload.create(sema.arena, val)); + } + switch (dest_err_set_ty.tag()) { .anyerror => {}, .error_set_single => { const expected_name = val.castTag(.@"error").?.data.name; @@ -8946,9 +8946,7 @@ fn wrapErrorUnion( }, else => unreachable, } - - // Create a SubValue for the error_union payload. - return sema.addConstant(dest_type, try Value.Tag.error_union.create(sema.arena, val)); + return sema.addConstant(dest_type, val); } try sema.requireRuntimeBlock(block, inst_src); diff --git a/src/codegen.zig b/src/codegen.zig index f5cdc518f6..38cc27d5bc 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4815,7 +4815,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .ErrorUnion => { const error_type = typed_value.ty.errorUnionSet(); const payload_type = typed_value.ty.errorUnionPayload(); - const sub_val = typed_value.val.castTag(.error_union).?.data; + const sub_val = typed_value.val.castTag(.eu_payload).?.data; if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 65ad4bac8e..a67e2438c2 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -350,32 +350,25 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = t.errorUnionSet(); const payload_type = t.errorUnionPayload(); - const sub_val = val.castTag(.error_union).?.data; if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return dg.renderValue(writer, error_type, sub_val); + const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val; + return dg.renderValue(writer, error_type, err_val); } try writer.writeByte('('); try dg.renderType(writer, t); try writer.writeAll("){"); - if (val.getError()) |_| { - try writer.writeAll(" .error = "); - try dg.renderValue( - writer, - error_type, - sub_val, - ); - try writer.writeAll(" }"); - } else { + if (val.castTag(.eu_payload)) |pl| { + const payload_val = pl.data; try writer.writeAll(" .payload = "); - try dg.renderValue( - writer, - payload_type, - sub_val, - ); + try dg.renderValue(writer, payload_type, payload_val); try writer.writeAll(", .error = 0 }"); + } else { + try writer.writeAll(" .error = "); + try dg.renderValue(writer, error_type, val); + try writer.writeAll(" }"); } }, .Enum => { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index eccd5fa04f..7cfbc8da5e 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -593,7 +593,7 @@ pub const DeclGen = struct { try self.llvmType(ptr_type), try self.llvmType(Type.initTag(.usize)), }; - return self.context.structType(&fields, 2, .False); + return self.context.structType(&fields, fields.len, .False); } else { const elem_type = try self.llvmType(t.elemType()); return elem_type.pointerType(0); @@ -621,10 +621,14 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = t.errorUnionSet(); const payload_type = t.errorUnionPayload(); + const llvm_error_type = try self.llvmType(error_type); if (!payload_type.hasCodeGenBits()) { - return self.llvmType(error_type); + return llvm_error_type; } - return self.todo("implement llvmType for error unions", .{}); + const llvm_payload_type = try self.llvmType(payload_type); + + const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type }; + return self.context.structType(&fields, fields.len, .False); }, .ErrorSet => { return self.context.intType(16); @@ -846,14 +850,25 @@ pub const DeclGen = struct { .ErrorUnion => { const error_type = tv.ty.errorUnionSet(); const payload_type = tv.ty.errorUnionPayload(); - const sub_val = tv.val.castTag(.error_union).?.data; + const is_pl = tv.val.errorUnionIsPayload(); if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. - return self.genTypedValue(.{ .ty = error_type, .val = sub_val }); + const err_val = if (!is_pl) tv.val else Value.initTag(.zero); + return self.genTypedValue(.{ .ty = error_type, .val = err_val }); } - return self.todo("implement error union const of type '{}'", .{tv.ty}); + const fields: [2]*const llvm.Value = .{ + try self.genTypedValue(.{ + .ty = error_type, + .val = if (is_pl) Value.initTag(.zero) else tv.val, + }), + try self.genTypedValue(.{ + .ty = payload_type, + .val = if (tv.val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef), + }), + }; + return self.context.constStruct(&fields, fields.len, .False); }, .Struct => { const fields_len = tv.ty.structFieldCount(); @@ -984,10 +999,10 @@ pub const FuncGen = struct { .is_non_null_ptr => try self.airIsNonNull(inst, true), .is_null => try self.airIsNull(inst, false), .is_null_ptr => try self.airIsNull(inst, true), - .is_non_err => try self.airIsErr(inst, true, false), - .is_non_err_ptr => try self.airIsErr(inst, true, true), - .is_err => try self.airIsErr(inst, false, false), - .is_err_ptr => try self.airIsErr(inst, false, true), + .is_non_err => try self.airIsErr(inst, .EQ, false), + .is_non_err_ptr => try self.airIsErr(inst, .EQ, true), + .is_err => try self.airIsErr(inst, .NE, false), + .is_err_ptr => try self.airIsErr(inst, .NE, true), .alloc => try self.airAlloc(inst), .arg => try self.airArg(inst), @@ -1098,7 +1113,7 @@ pub const FuncGen = struct { const inst_ty = self.air.typeOfIndex(inst); switch (self.air.typeOf(bin_op.lhs).zigTypeTag()) { - .Int, .Bool, .Pointer => { + .Int, .Bool, .Pointer, .ErrorSet => { const is_signed = inst_ty.isSignedInt(); const operation = switch (op) { .eq => .EQ, @@ -1256,12 +1271,7 @@ pub const FuncGen = struct { const rhs = try self.resolveInst(bin_op.rhs); const base_ptr = ptr: { - const index_type = self.context.intType(32); - const indices: [2]*const llvm.Value = .{ - index_type.constNull(), - index_type.constInt(0, .False), - }; - const ptr_field_ptr = self.builder.buildInBoundsGEP(lhs, &indices, 2, ""); + const ptr_field_ptr = self.builder.buildStructGEP(lhs, 0, ""); break :ptr self.builder.buildLoad(ptr_field_ptr, ""); }; @@ -1472,7 +1482,7 @@ pub const FuncGen = struct { index_type.constInt(1, .False), }; - return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, 2, ""), ""); + return self.builder.buildLoad(self.builder.buildInBoundsGEP(operand, &indices, indices.len, ""), ""); } else { return self.builder.buildExtractValue(operand, 1, ""); } @@ -1488,7 +1498,7 @@ pub const FuncGen = struct { fn airIsErr( self: *FuncGen, inst: Air.Inst.Index, - invert_logic: bool, + op: llvm.IntPredicate, operand_is_ptr: bool, ) !?*const llvm.Value { if (self.liveness.isUnused(inst)) @@ -1498,16 +1508,22 @@ pub const FuncGen = struct { const operand = try self.resolveInst(un_op); const err_union_ty = self.air.typeOf(un_op); const payload_ty = err_union_ty.errorUnionPayload(); + const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror)); + const zero = err_set_ty.constNull(); if (!payload_ty.hasCodeGenBits()) { const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand; - const op: llvm.IntPredicate = if (invert_logic) .EQ else .NE; - const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror)); - const zero = err_set_ty.constNull(); return self.builder.buildICmp(op, loaded, zero, ""); } - return self.todo("implement 'airIsErr' for error unions with nonzero payload", .{}); + if (operand_is_ptr) { + const err_field_ptr = self.builder.buildStructGEP(operand, 0, ""); + const loaded = self.builder.buildLoad(err_field_ptr, ""); + return self.builder.buildICmp(op, loaded, zero, ""); + } + + const loaded = self.builder.buildExtractValue(operand, 0, ""); + return self.builder.buildICmp(op, loaded, zero, ""); } fn airOptionalPayload( @@ -1552,9 +1568,11 @@ pub const FuncGen = struct { return null; } - _ = operand; - _ = operand_is_ptr; - return self.todo("implement llvm codegen for 'airErrUnionPayload' for type {}", .{self.air.typeOf(ty_op.operand)}); + if (operand_is_ptr) { + return self.builder.buildStructGEP(operand, 1, ""); + } + + return self.builder.buildExtractValue(operand, 1, ""); } fn airErrUnionErr( @@ -1574,7 +1592,13 @@ pub const FuncGen = struct { if (!operand_is_ptr) return operand; return self.builder.buildLoad(operand, ""); } - return self.todo("implement llvm codegen for 'airErrUnionErr'", .{}); + + if (operand_is_ptr) { + const err_field_ptr = self.builder.buildStructGEP(operand, 0, ""); + return self.builder.buildLoad(err_field_ptr, ""); + } + + return self.builder.buildExtractValue(operand, 0, ""); } fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { diff --git a/src/codegen/wasm.zig b/src/codegen/wasm.zig index e21645d1ee..4814ba0b55 100644 --- a/src/codegen/wasm.zig +++ b/src/codegen/wasm.zig @@ -1167,12 +1167,18 @@ pub const Context = struct { try leb.writeULEB128(writer, error_index); }, .ErrorUnion => { - const data = val.castTag(.error_union).?.data; const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); - if (val.getError()) |_| { + if (val.castTag(.eu_payload)) |pl| { + const payload_val = pl.data; + // no error, so write a '0' const + try writer.writeByte(wasm.opcode(.i32_const)); + try leb.writeULEB128(writer, @as(u32, 0)); + // after the error code, we emit the payload + try self.emitConstant(payload_val, payload_type); + } else { // write the error val - try self.emitConstant(data, error_type); + try self.emitConstant(val, error_type); // no payload, so write a '0' const const opcode: wasm.Opcode = buildOpcode(.{ @@ -1181,12 +1187,6 @@ pub const Context = struct { }); try writer.writeByte(wasm.opcode(opcode)); try leb.writeULEB128(writer, @as(u32, 0)); - } else { - // no error, so write a '0' const - try writer.writeByte(wasm.opcode(.i32_const)); - try leb.writeULEB128(writer, @as(u32, 0)); - // after the error code, we emit the payload - try self.emitConstant(data, payload_type); } }, .Optional => { diff --git a/src/value.zig b/src/value.zig index bf80c9d831..562d7171e8 100644 --- a/src/value.zig +++ b/src/value.zig @@ -129,7 +129,13 @@ pub const Value = extern union { /// A specific enum tag, indicated by the field index (declaration order). enum_field_index, @"error", - error_union, + /// When the type is error union: + /// * If the tag is `.@"error"`, the error union is an error. + /// * If the tag is `.eu_payload`, the error union is a payload. + /// * A nested error such as `((anyerror!T1)!T2)` in which the the outer error union + /// is non-error, but the inner error union is an error, is represented as + /// a tag of `.eu_payload`, with a sub-tag of `.@"error"`. + eu_payload, /// A pointer to the payload of an error union, based on a pointer to an error union. eu_payload_ptr, /// An instance of a struct. @@ -228,7 +234,7 @@ pub const Value = extern union { => Payload.Decl, .repeated, - .error_union, + .eu_payload, .eu_payload_ptr, => Payload.SubValue, @@ -450,7 +456,7 @@ pub const Value = extern union { return Value{ .ptr_otherwise = &new_payload.base }; }, .bytes => return self.copyPayloadShallow(allocator, Payload.Bytes), - .repeated, .error_union, .eu_payload_ptr => { + .repeated, .eu_payload, .eu_payload_ptr => { const payload = self.cast(Payload.SubValue).?; const new_payload = try allocator.create(Payload.SubValue); new_payload.* = .{ @@ -642,7 +648,10 @@ pub const Value = extern union { .float_128 => return out_stream.print("{}", .{val.castTag(.float_128).?.data}), .@"error" => return out_stream.print("error.{s}", .{val.castTag(.@"error").?.data.name}), // TODO to print this it should be error{ Set, Items }!T(val), but we need the type for that - .error_union => return out_stream.print("error_union_val({})", .{val.castTag(.error_union).?.data}), + .eu_payload => { + try out_stream.writeAll("(eu_payload) "); + val = val.castTag(.eu_payload).?.data; + }, .inferred_alloc => return out_stream.writeAll("(inferred allocation value)"), .inferred_alloc_comptime => return out_stream.writeAll("(inferred comptime allocation value)"), .eu_payload_ptr => { @@ -1241,7 +1250,7 @@ pub const Value = extern union { .eu_payload_ptr => blk: { const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null; - break :blk err_union_val.castTag(.error_union).?.data; + break :blk err_union_val.castTag(.eu_payload).?.data; }, .zero, @@ -1351,16 +1360,16 @@ pub const Value = extern union { } /// Valid for all types. Asserts the value is not undefined and not unreachable. + /// Prefer `errorUnionIsPayload` to find out whether something is an error or not + /// because it works without having to figure out the string. pub fn getError(self: Value) ?[]const u8 { return switch (self.tag()) { - .error_union => { - const data = self.castTag(.error_union).?.data; - return if (data.tag() == .@"error") - data.castTag(.@"error").?.data.name - else - null; - }, .@"error" => self.castTag(.@"error").?.data.name, + .int_u64 => @panic("TODO"), + .int_i64 => @panic("TODO"), + .int_big_positive => @panic("TODO"), + .int_big_negative => @panic("TODO"), + .one => @panic("TODO"), .undef => unreachable, .unreachable_value => unreachable, .inferred_alloc => unreachable, @@ -1369,6 +1378,16 @@ pub const Value = extern union { else => null, }; } + + /// Assumes the type is an error union. Returns true if and only if the value is + /// the error union payload, not an error. + pub fn errorUnionIsPayload(val: Value) bool { + return switch (val.tag()) { + .eu_payload => true, + else => false, + }; + } + /// Valid for all types. Asserts the value is not undefined. pub fn isFloat(self: Value) bool { return switch (self.tag()) { diff --git a/test/behavior.zig b/test/behavior.zig index 936268af9c..a800b38458 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -7,6 +7,7 @@ test { _ = @import("behavior/generics.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/pointers.zig"); + _ = @import("behavior/if.zig"); if (!builtin.zig_is_stage2) { // Tests that only pass for stage1. @@ -100,7 +101,7 @@ test { _ = @import("behavior/generics_stage1.zig"); _ = @import("behavior/hasdecl.zig"); _ = @import("behavior/hasfield.zig"); - _ = @import("behavior/if.zig"); + _ = @import("behavior/if_stage1.zig"); _ = @import("behavior/import.zig"); _ = @import("behavior/incomplete_struct_param_tld.zig"); _ = @import("behavior/inttoptr.zig"); diff --git a/test/behavior/if.zig b/test/behavior/if.zig index e8c84f4570..191d4817df 100644 --- a/test/behavior/if.zig +++ b/test/behavior/if.zig @@ -65,45 +65,3 @@ test "labeled break inside comptime if inside runtime if" { } try expect(answer == 42); } - -test "const result loc, runtime if cond, else unreachable" { - const Num = enum { - One, - Two, - }; - - var t = true; - const x = if (t) Num.Two else unreachable; - try expect(x == .Two); -} - -test "if prongs cast to expected type instead of peer type resolution" { - const S = struct { - fn doTheTest(f: bool) !void { - var x: i32 = 0; - x = if (f) 1 else 2; - try expect(x == 2); - - var b = true; - const y: i32 = if (b) 1 else 2; - try expect(y == 1); - } - }; - try S.doTheTest(false); - comptime try S.doTheTest(false); -} - -test "while copies its payload" { - const S = struct { - fn doTheTest() !void { - var tmp: ?i32 = 10; - if (tmp) |value| { - // Modify the original variable - tmp = null; - try expectEqual(@as(i32, 10), value); - } else unreachable; - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} diff --git a/test/behavior/if_stage1.zig b/test/behavior/if_stage1.zig new file mode 100644 index 0000000000..36500fbaee --- /dev/null +++ b/test/behavior/if_stage1.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; + +test "const result loc, runtime if cond, else unreachable" { + const Num = enum { + One, + Two, + }; + + var t = true; + const x = if (t) Num.Two else unreachable; + try expect(x == .Two); +} + +test "if prongs cast to expected type instead of peer type resolution" { + const S = struct { + fn doTheTest(f: bool) !void { + var x: i32 = 0; + x = if (f) 1 else 2; + try expect(x == 2); + + var b = true; + const y: i32 = if (b) 1 else 2; + try expect(y == 1); + } + }; + try S.doTheTest(false); + comptime try S.doTheTest(false); +} + +test "while copies its payload" { + const S = struct { + fn doTheTest() !void { + var tmp: ?i32 = 10; + if (tmp) |value| { + // Modify the original variable + tmp = null; + try expectEqual(@as(i32, 10), value); + } else unreachable; + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} -- cgit v1.2.3