From cd8070f94f6865959949dbcec6e0e32cd88bb544 Mon Sep 17 00:00:00 2001 From: antlilja Date: Sat, 30 Jul 2022 11:39:49 +0200 Subject: Removed param_names from Fn inside Module.zig Removed the copy of param_names inside of Fn and changed to implementation of getParamName to fetch to parameter name from the ZIR. The signature of getParamName was also changed to take an additional *Module argument. --- src/codegen/llvm.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 664edb0304..5ea7ffad67 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -7313,7 +7313,7 @@ pub const FuncGen = struct { const lbrace_col = func.lbrace_column + 1; const di_local_var = dib.createParameterVariable( self.di_scope.?, - func.getParamName(src_index).ptr, // TODO test 0 bit args + func.getParamName(self.dg.module, src_index).ptr, // TODO test 0 bit args self.di_file.?, lbrace_line, try self.dg.object.lowerDebugType(inst_ty, .full), -- cgit v1.2.3 From f46d7304b176cdc77053225943a7d5030dd0d4ee Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Fri, 5 Aug 2022 18:15:31 +0300 Subject: stage2: add runtime safety for invalid enum values --- src/Air.zig | 5 ++ src/Liveness.zig | 2 + src/Sema.zig | 15 +++- src/arch/aarch64/CodeGen.zig | 2 + src/arch/arm/CodeGen.zig | 2 + src/arch/riscv64/CodeGen.zig | 2 + src/arch/sparc64/CodeGen.zig | 2 + src/arch/wasm/CodeGen.zig | 1 + src/arch/x86_64/CodeGen.zig | 2 + src/codegen/c.zig | 2 + src/codegen/llvm.zig | 87 ++++++++++++++++++++++ src/print_air.zig | 1 + .../safety/@intToEnum - no matching tag value.zig | 9 ++- .../safety/@tagName on corrupted enum value.zig | 3 +- .../safety/@tagName on corrupted union value.zig | 3 +- 15 files changed, 131 insertions(+), 7 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Air.zig b/src/Air.zig index 302822fc99..c5734278d3 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -660,6 +660,10 @@ pub const Inst = struct { /// Uses the `pl_op` field with payload `AtomicRmw`. Operand is `ptr`. atomic_rmw, + /// Returns true if enum tag value has a name. + /// Uses the `un_op` field. + is_named_enum_value, + /// Given an enum tag value, returns the tag name. The enum type may be non-exhaustive. /// Result type is always `[:0]const u8`. /// Uses the `un_op` field. @@ -1057,6 +1061,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .is_non_err, .is_err_ptr, .is_non_err_ptr, + .is_named_enum_value, => return Type.bool, .const_ty => return Type.type, diff --git a/src/Liveness.zig b/src/Liveness.zig index 435075a411..748016d584 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -291,6 +291,7 @@ pub fn categorizeOperand( .is_non_err_ptr, .ptrtoint, .bool_to_int, + .is_named_enum_value, .tag_name, .error_name, .sqrt, @@ -858,6 +859,7 @@ fn analyzeInst( .bool_to_int, .ret, .ret_load, + .is_named_enum_value, .tag_name, .error_name, .sqrt, diff --git a/src/Sema.zig b/src/Sema.zig index 3b672ecd42..56e08d081b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6933,8 +6933,12 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } try sema.requireRuntimeBlock(block, src, operand_src); - // TODO insert safety check to make sure the value matches an enum value - return block.addTyOp(.intcast, dest_ty, operand); + const result = try block.addTyOp(.intcast, dest_ty, operand); + if (block.wantSafety() and !dest_ty.isNonexhaustiveEnum() and sema.mod.comp.bin_file.options.use_llvm) { + const ok = try block.addUnOp(.is_named_enum_value, result); + try sema.addSafetyCheck(block, ok, .invalid_enum_value); + } + return result; } /// Pointer in, pointer out. @@ -15887,6 +15891,11 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const field_name = enum_ty.enumFieldName(field_index); return sema.addStrLit(block, field_name); } + try sema.requireRuntimeBlock(block, src, operand_src); + if (block.wantSafety() and sema.mod.comp.bin_file.options.use_llvm) { + const ok = try block.addUnOp(.is_named_enum_value, casted_operand); + try sema.addSafetyCheck(block, ok, .invalid_enum_value); + } // In case the value is runtime-known, we have an AIR instruction for this instead // of trying to lower it in Sema because an optimization pass may result in the operand // being comptime-known, which would let us elide the `tag_name` AIR instruction. @@ -20019,6 +20028,7 @@ pub const PanicId = enum { integer_part_out_of_bounds, corrupt_switch, shift_rhs_too_big, + invalid_enum_value, }; fn addSafetyCheck( @@ -20316,6 +20326,7 @@ fn safetyPanic( .integer_part_out_of_bounds => "integer part of floating point value out of bounds", .corrupt_switch => "switch on corrupt value", .shift_rhs_too_big => "shift amount is greater than the type size", + .invalid_enum_value => "invalid enum value", }; const msg_inst = msg_inst: { diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index a8bafee4f8..369cabecea 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -753,6 +753,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index a603f72d53..be2891ef6f 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -768,6 +768,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index d09c6278b5..a5007928b6 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -693,6 +693,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index bc9fc115b2..bf834a36d9 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -705,6 +705,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => @panic("TODO implement optimized float mode"), + .is_named_enum_value => @panic("TODO implement is_named_enum_value"), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index dd9cfba548..ef92361de2 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1621,6 +1621,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .tag_name, .err_return_trace, .set_err_return_trace, + .is_named_enum_value, => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), .add_optimized, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index d59a114e51..7f7473bc66 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -775,6 +775,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .float_to_int_optimized, => return self.fail("TODO implement optimized float mode", .{}), + .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, // zig fmt: on diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 280b7604bf..b746818142 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1952,6 +1952,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .reduce_optimized, .float_to_int_optimized, => return f.fail("TODO implement optimized float mode", .{}), + + .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), // zig fmt: on }; switch (result_value) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5ea7ffad67..9c3efa18cd 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -201,6 +201,8 @@ pub const Object = struct { /// * it works for functions not all globals. /// Therefore, this table keeps track of the mapping. decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value), + /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction. + named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *const llvm.Value), /// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of /// the compiler, but the Type/Value memory here is backed by `type_map_arena`. /// TODO we need to remove entries from this map in response to incremental compilation @@ -377,6 +379,7 @@ pub const Object = struct { .target_data = target_data, .target = options.target, .decl_map = .{}, + .named_enum_map = .{}, .type_map = .{}, .type_map_arena = std.heap.ArenaAllocator.init(gpa), .di_type_map = .{}, @@ -396,6 +399,7 @@ pub const Object = struct { self.llvm_module.dispose(); self.context.dispose(); self.decl_map.deinit(gpa); + self.named_enum_map.deinit(gpa); self.type_map.deinit(gpa); self.type_map_arena.deinit(); self.extern_collisions.deinit(gpa); @@ -4180,6 +4184,8 @@ pub const FuncGen = struct { .union_init => try self.airUnionInit(inst), .prefetch => try self.airPrefetch(inst), + .is_named_enum_value => try self.airIsNamedEnumValue(inst), + .reduce => try self.airReduce(inst, false), .reduce_optimized => try self.airReduce(inst, true), @@ -7882,6 +7888,87 @@ pub const FuncGen = struct { } } + fn airIsNamedEnumValue(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); + const enum_ty = self.air.typeOf(un_op); + + const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty); + const params = [_]*const llvm.Value{operand}; + return self.builder.buildCall(llvm_fn, ¶ms, params.len, .Fast, .Auto, ""); + } + + fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*const llvm.Value { + const enum_decl = enum_ty.getOwnerDecl(); + + // TODO: detect when the type changes and re-emit this function. + const gop = try self.dg.object.named_enum_map.getOrPut(self.dg.gpa, enum_decl); + if (gop.found_existing) return gop.value_ptr.*; + errdefer assert(self.dg.object.named_enum_map.remove(enum_decl)); + + var arena_allocator = std.heap.ArenaAllocator.init(self.gpa); + defer arena_allocator.deinit(); + const arena = arena_allocator.allocator(); + + const mod = self.dg.module; + const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{ + try mod.declPtr(enum_decl).getFullyQualifiedName(mod), + }); + + var int_tag_type_buffer: Type.Payload.Bits = undefined; + const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer); + const param_types = [_]*const llvm.Type{try self.dg.lowerType(int_tag_ty)}; + + const llvm_ret_ty = try self.dg.lowerType(Type.bool); + const fn_type = llvm.functionType(llvm_ret_ty, ¶m_types, param_types.len, .False); + const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type); + fn_val.setLinkage(.Internal); + fn_val.setFunctionCallConv(.Fast); + self.dg.addCommonFnAttributes(fn_val); + gop.value_ptr.* = fn_val; + + const prev_block = self.builder.getInsertBlock(); + const prev_debug_location = self.builder.getCurrentDebugLocation2(); + defer { + self.builder.positionBuilderAtEnd(prev_block); + if (self.di_scope != null) { + self.builder.setCurrentDebugLocation2(prev_debug_location); + } + } + + const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry"); + self.builder.positionBuilderAtEnd(entry_block); + self.builder.clearCurrentDebugLocation(); + + const fields = enum_ty.enumFields(); + const named_block = self.dg.context.appendBasicBlock(fn_val, "Named"); + const unnamed_block = self.dg.context.appendBasicBlock(fn_val, "Unnamed"); + const tag_int_value = fn_val.getParam(0); + const switch_instr = self.builder.buildSwitch(tag_int_value, unnamed_block, @intCast(c_uint, fields.count())); + + for (fields.keys()) |_, field_index| { + const this_tag_int_value = int: { + var tag_val_payload: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = @intCast(u32, field_index), + }; + break :int try self.dg.lowerValue(.{ + .ty = enum_ty, + .val = Value.initPayload(&tag_val_payload.base), + }); + }; + switch_instr.addCase(this_tag_int_value, named_block); + } + self.builder.positionBuilderAtEnd(named_block); + _ = self.builder.buildRet(self.dg.context.intType(1).constInt(1, .False)); + + self.builder.positionBuilderAtEnd(unnamed_block); + _ = self.builder.buildRet(self.dg.context.intType(1).constInt(0, .False)); + return fn_val; + } + fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index ec4a94b420..23107946f6 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -170,6 +170,7 @@ const Writer = struct { .bool_to_int, .ret, .ret_load, + .is_named_enum_value, .tag_name, .error_name, .sqrt, diff --git a/test/cases/safety/@intToEnum - no matching tag value.zig b/test/cases/safety/@intToEnum - no matching tag value.zig index 79fcf33bc6..0e5f401f6d 100644 --- a/test/cases/safety/@intToEnum - no matching tag value.zig +++ b/test/cases/safety/@intToEnum - no matching tag value.zig @@ -1,9 +1,11 @@ const std = @import("std"); pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { - _ = message; _ = stack_trace; - std.process.exit(0); + if (std.mem.eql(u8, message, "invalid enum value")) { + std.process.exit(0); + } + std.process.exit(1); } const Foo = enum { A, @@ -18,6 +20,7 @@ fn bar(a: u2) Foo { return @intToEnum(Foo, a); } fn baz(_: Foo) void {} + // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/@tagName on corrupted enum value.zig b/test/cases/safety/@tagName on corrupted enum value.zig index 507157911e..4081d171c4 100644 --- a/test/cases/safety/@tagName on corrupted enum value.zig +++ b/test/cases/safety/@tagName on corrupted enum value.zig @@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const E = enum(u32) { X = 1, + Y = 2, }; pub fn main() !void { @@ -21,5 +22,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native diff --git a/test/cases/safety/@tagName on corrupted union value.zig b/test/cases/safety/@tagName on corrupted union value.zig index 0c35b5ef3d..eb36fab262 100644 --- a/test/cases/safety/@tagName on corrupted union value.zig +++ b/test/cases/safety/@tagName on corrupted union value.zig @@ -10,6 +10,7 @@ pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noretur const U = union(enum(u32)) { X: u8, + Y: i8, }; pub fn main() !void { @@ -22,5 +23,5 @@ pub fn main() !void { } // run -// backend=stage1 +// backend=llvm // target=native -- cgit v1.2.3 From 0d32b73078aa4579187f7d5c67343a6036eed277 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 8 Aug 2022 18:39:14 +0200 Subject: stage2: Implement explicit backing integers for packed structs Now the backing integer of a packed struct type may be explicitly specified with e.g. `packed struct(u32) { ... }`. --- lib/std/builtin.zig | 2 + lib/std/zig/Ast.zig | 2 +- lib/std/zig/parse.zig | 10 +- lib/std/zig/parser_test.zig | 7 + src/AstGen.zig | 56 +++++- src/Autodoc.zig | 11 ++ src/Module.zig | 21 +-- src/Sema.zig | 207 ++++++++++++++++++++- src/Zir.zig | 16 +- src/codegen/llvm.zig | 15 +- src/print_zir.zig | 25 ++- src/stage1/all_types.hpp | 1 + src/stage1/analyze.cpp | 6 + src/stage1/ir.cpp | 40 ++-- src/stage1/parser.cpp | 11 +- src/type.zig | 23 ++- test/behavior.zig | 1 + .../packed_struct_explicit_backing_int.zig | 53 ++++++ test/behavior/type_info.zig | 4 +- .../packed_struct_backing_int_wrong.zig | 55 ++++++ 20 files changed, 493 insertions(+), 73 deletions(-) create mode 100644 test/behavior/packed_struct_explicit_backing_int.zig create mode 100644 test/cases/compile_errors/packed_struct_backing_int_wrong.zig (limited to 'src/codegen/llvm.zig') diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index ef716c6972..2c2bc92c96 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -294,6 +294,8 @@ pub const Type = union(enum) { /// therefore must be kept in sync with the compiler implementation. pub const Struct = struct { layout: ContainerLayout, + /// Only valid if layout is .Packed + backing_integer: ?type = null, fields: []const StructField, decls: []const Declaration, is_tuple: bool, diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 9bffcb3df2..016cefb255 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -2967,7 +2967,7 @@ pub const Node = struct { /// Same as ContainerDeclTwo except there is known to be a trailing comma /// or semicolon before the rbrace. container_decl_two_trailing, - /// `union(lhs)` / `enum(lhs)`. `SubRange[rhs]`. + /// `struct(lhs)` / `union(lhs)` / `enum(lhs)`. `SubRange[rhs]`. container_decl_arg, /// Same as container_decl_arg but there is known to be a trailing /// comma or semicolon before the rbrace. diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 2a7d2623ef..a03764a91c 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -3356,16 +3356,18 @@ const Parser = struct { } /// Caller must have already verified the first token. + /// ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE + /// /// ContainerDeclType - /// <- KEYWORD_struct + /// <- KEYWORD_struct (LPAREN Expr RPAREN)? + /// / KEYWORD_opaque /// / KEYWORD_enum (LPAREN Expr RPAREN)? /// / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? - /// / KEYWORD_opaque fn parseContainerDeclAuto(p: *Parser) !Node.Index { const main_token = p.nextToken(); const arg_expr = switch (p.token_tags[main_token]) { - .keyword_struct, .keyword_opaque => null_node, - .keyword_enum => blk: { + .keyword_opaque => null_node, + .keyword_struct, .keyword_enum => blk: { if (p.eatToken(.l_paren)) |_| { const expr = try p.expectExpr(); _ = try p.expectToken(.r_paren); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index a74d53f21c..bee9375b5a 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -3064,6 +3064,13 @@ test "zig fmt: struct declaration" { \\ c: u8, \\}; \\ + \\const Ps = packed struct(u32) { + \\ a: u1, + \\ b: u2, + \\ + \\ c: u29, + \\}; + \\ \\const Es = extern struct { \\ a: u8, \\ b: u8, diff --git a/src/AstGen.zig b/src/AstGen.zig index efa8690b55..af77cdacc4 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -152,6 +152,7 @@ pub fn generate(gpa: Allocator, tree: Ast) Allocator.Error!Zir { 0, tree.containerDeclRoot(), .Auto, + 0, )) |struct_decl_ref| { assert(refToIndex(struct_decl_ref).? == 0); } else |err| switch (err) { @@ -4223,15 +4224,18 @@ fn structDeclInner( node: Ast.Node.Index, container_decl: Ast.full.ContainerDecl, layout: std.builtin.Type.ContainerLayout, + backing_int_node: Ast.Node.Index, ) InnerError!Zir.Inst.Ref { const decl_inst = try gz.reserveInstructionIndex(); - if (container_decl.ast.members.len == 0) { + if (container_decl.ast.members.len == 0 and backing_int_node == 0) { try gz.setStruct(decl_inst, .{ .src_node = node, .layout = layout, .fields_len = 0, .decls_len = 0, + .backing_int_ref = .none, + .backing_int_body_len = 0, .known_non_opv = false, .known_comptime_only = false, }); @@ -4266,6 +4270,35 @@ fn structDeclInner( }; defer block_scope.unstack(); + const scratch_top = astgen.scratch.items.len; + defer astgen.scratch.items.len = scratch_top; + + var backing_int_body_len: usize = 0; + const backing_int_ref: Zir.Inst.Ref = blk: { + if (backing_int_node != 0) { + if (layout != .Packed) { + return astgen.failNode(backing_int_node, "non-packed struct does not support backing integer type", .{}); + } else { + const backing_int_ref = try typeExpr(&block_scope, &namespace.base, backing_int_node); + if (!block_scope.isEmpty()) { + if (!block_scope.endsWithNoReturn()) { + _ = try block_scope.addBreak(.break_inline, decl_inst, backing_int_ref); + } + + const body = block_scope.instructionsSlice(); + const old_scratch_len = astgen.scratch.items.len; + try astgen.scratch.ensureUnusedCapacity(gpa, countBodyLenAfterFixups(astgen, body)); + appendBodyWithFixupsArrayList(astgen, &astgen.scratch, body); + backing_int_body_len = astgen.scratch.items.len - old_scratch_len; + block_scope.instructions.items.len = block_scope.instructions_top; + } + break :blk backing_int_ref; + } + } else { + break :blk .none; + } + }; + const decl_count = try astgen.scanDecls(&namespace, container_decl.ast.members); const field_count = @intCast(u32, container_decl.ast.members.len - decl_count); @@ -4378,6 +4411,8 @@ fn structDeclInner( .layout = layout, .fields_len = field_count, .decls_len = decl_count, + .backing_int_ref = backing_int_ref, + .backing_int_body_len = @intCast(u32, backing_int_body_len), .known_non_opv = known_non_opv, .known_comptime_only = known_comptime_only, }); @@ -4386,7 +4421,9 @@ fn structDeclInner( const decls_slice = wip_members.declsSlice(); const fields_slice = wip_members.fieldsSlice(); const bodies_slice = astgen.scratch.items[bodies_start..]; - try astgen.extra.ensureUnusedCapacity(gpa, decls_slice.len + fields_slice.len + bodies_slice.len); + try astgen.extra.ensureUnusedCapacity(gpa, backing_int_body_len + + decls_slice.len + fields_slice.len + bodies_slice.len); + astgen.extra.appendSliceAssumeCapacity(astgen.scratch.items[scratch_top..][0..backing_int_body_len]); astgen.extra.appendSliceAssumeCapacity(decls_slice); astgen.extra.appendSliceAssumeCapacity(fields_slice); astgen.extra.appendSliceAssumeCapacity(bodies_slice); @@ -4582,9 +4619,7 @@ fn containerDecl( else => unreachable, } else std.builtin.Type.ContainerLayout.Auto; - assert(container_decl.ast.arg == 0); - - const result = try structDeclInner(gz, scope, node, container_decl, layout); + const result = try structDeclInner(gz, scope, node, container_decl, layout, container_decl.ast.arg); return rvalue(gz, rl, result, node); }, .keyword_union => { @@ -11254,6 +11289,8 @@ const GenZir = struct { src_node: Ast.Node.Index, fields_len: u32, decls_len: u32, + backing_int_ref: Zir.Inst.Ref, + backing_int_body_len: u32, layout: std.builtin.Type.ContainerLayout, known_non_opv: bool, known_comptime_only: bool, @@ -11261,7 +11298,7 @@ const GenZir = struct { const astgen = gz.astgen; const gpa = astgen.gpa; - try astgen.extra.ensureUnusedCapacity(gpa, 4); + try astgen.extra.ensureUnusedCapacity(gpa, 6); const payload_index = @intCast(u32, astgen.extra.items.len); if (args.src_node != 0) { @@ -11274,6 +11311,12 @@ const GenZir = struct { if (args.decls_len != 0) { astgen.extra.appendAssumeCapacity(args.decls_len); } + if (args.backing_int_ref != .none) { + astgen.extra.appendAssumeCapacity(args.backing_int_body_len); + if (args.backing_int_body_len == 0) { + astgen.extra.appendAssumeCapacity(@enumToInt(args.backing_int_ref)); + } + } astgen.instructions.set(inst, .{ .tag = .extended, .data = .{ .extended = .{ @@ -11282,6 +11325,7 @@ const GenZir = struct { .has_src_node = args.src_node != 0, .has_fields_len = args.fields_len != 0, .has_decls_len = args.decls_len != 0, + .has_backing_int = args.backing_int_ref != .none, .known_non_opv = args.known_non_opv, .known_comptime_only = args.known_comptime_only, .name_strategy = gz.anon_name_strategy, diff --git a/src/Autodoc.zig b/src/Autodoc.zig index 3fcc28d742..ef2579a8e4 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -2536,6 +2536,17 @@ fn walkInstruction( break :blk decls_len; } else 0; + // TODO: Expose explicit backing integer types in some way. + if (small.has_backing_int) { + const backing_int_body_len = file.zir.extra[extra_index]; + extra_index += 1; // backing_int_body_len + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + extra_index += backing_int_body_len; // backing_int_body_inst + } + } + var decl_indexes: std.ArrayListUnmanaged(usize) = .{}; var priv_decl_indexes: std.ArrayListUnmanaged(usize) = .{}; diff --git a/src/Module.zig b/src/Module.zig index 8b195eff2d..7e877a2f4a 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -895,6 +895,11 @@ pub const Struct = struct { zir_index: Zir.Inst.Index, layout: std.builtin.Type.ContainerLayout, + /// If the layout is not packed, this is the noreturn type. + /// If the layout is packed, this is the backing integer type of the packed struct. + /// Whether zig chooses this type or the user specifies it, it is stored here. + /// This will be set to the noreturn type until status is `have_layout`. + backing_int_ty: Type = Type.initTag(.noreturn), status: enum { none, field_types_wip, @@ -1025,7 +1030,7 @@ pub const Struct = struct { pub fn packedFieldBitOffset(s: Struct, target: Target, index: usize) u16 { assert(s.layout == .Packed); - assert(s.haveFieldTypes()); + assert(s.haveLayout()); var bit_sum: u64 = 0; for (s.fields.values()) |field, i| { if (i == index) { @@ -1033,19 +1038,7 @@ pub const Struct = struct { } bit_sum += field.ty.bitSize(target); } - return @intCast(u16, bit_sum); - } - - pub fn packedIntegerBits(s: Struct, target: Target) u16 { - return s.packedFieldBitOffset(target, s.fields.count()); - } - - pub fn packedIntegerType(s: Struct, target: Target, buf: *Type.Payload.Bits) Type { - buf.* = .{ - .base = .{ .tag = .int_unsigned }, - .data = s.packedIntegerBits(target), - }; - return Type.initPayload(&buf.base); + unreachable; // index out of bounds } }; diff --git a/src/Sema.zig b/src/Sema.zig index 6d95b46c7c..faf1692653 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2239,6 +2239,16 @@ pub fn analyzeStructDecl( break :blk decls_len; } else 0; + if (small.has_backing_int) { + const backing_int_body_len = sema.code.extra[extra_index]; + extra_index += 1; // backing_int_body_len + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + extra_index += backing_int_body_len; // backing_int_body_inst + } + } + _ = try sema.mod.scanNamespace(&struct_obj.namespace, extra_index, decls_len, new_decl); } @@ -14285,13 +14295,27 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, struct_ty.getNamespace()); - const field_values = try sema.arena.create([4]Value); + const backing_integer_val = blk: { + if (layout == .Packed) { + const struct_obj = struct_ty.castTag(.@"struct").?.data; + assert(struct_obj.haveLayout()); + assert(struct_obj.backing_int_ty.isInt()); + const backing_int_ty_val = try Value.Tag.ty.create(sema.arena, struct_obj.backing_int_ty); + break :blk try Value.Tag.opt_payload.create(sema.arena, backing_int_ty_val); + } else { + break :blk Value.initTag(.null_value); + } + }; + + const field_values = try sema.arena.create([5]Value); field_values.* = .{ // layout: ContainerLayout, try Value.Tag.enum_field_index.create( sema.arena, @enumToInt(layout), ), + // backing_integer: ?type, + backing_integer_val, // fields: []const StructField, fields_val, // decls: []const Declaration, @@ -16308,7 +16332,7 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in if (!try sema.intFitsInType(block, src, alignment_val, Type.u32, null)) { return sema.fail(block, src, "alignment must fit in 'u32'", .{}); } - const abi_align = @intCast(u29, alignment_val.toUnsignedInt(target)); + const abi_align = @intCast(u29, (try alignment_val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?); var buffer: Value.ToTypeBuffer = undefined; const unresolved_elem_ty = child_val.toType(&buffer); @@ -16473,22 +16497,31 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in const struct_val = union_val.val.castTag(.aggregate).?.data; // layout: containerlayout, const layout_val = struct_val[0]; + // backing_int: ?type, + const backing_int_val = struct_val[1]; // fields: []const enumfield, - const fields_val = struct_val[1]; + const fields_val = struct_val[2]; // decls: []const declaration, - const decls_val = struct_val[2]; + const decls_val = struct_val[3]; // is_tuple: bool, - const is_tuple_val = struct_val[3]; + const is_tuple_val = struct_val[4]; + assert(struct_val.len == 5); + + const layout = layout_val.toEnum(std.builtin.Type.ContainerLayout); // Decls if (decls_val.sliceLen(mod) > 0) { return sema.fail(block, src, "reified structs must have no decls", .{}); } + if (layout != .Packed and !backing_int_val.isNull()) { + return sema.fail(block, src, "non-packed struct does not support backing integer type", .{}); + } + return if (is_tuple_val.toBool()) try sema.reifyTuple(block, src, fields_val) else - try sema.reifyStruct(block, inst, src, layout_val, fields_val, name_strategy); + try sema.reifyStruct(block, inst, src, layout, backing_int_val, fields_val, name_strategy); }, .Enum => { const struct_val = union_val.val.castTag(.aggregate).?.data; @@ -16981,7 +17014,8 @@ fn reifyStruct( block: *Block, inst: Zir.Inst.Index, src: LazySrcLoc, - layout_val: Value, + layout: std.builtin.Type.ContainerLayout, + backing_int_val: Value, fields_val: Value, name_strategy: Zir.Inst.NameStrategy, ) CompileError!Air.Inst.Ref { @@ -17004,7 +17038,7 @@ fn reifyStruct( .owner_decl = new_decl_index, .fields = .{}, .zir_index = inst, - .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .layout = layout, .status = .have_field_types, .known_non_opv = false, .namespace = .{ @@ -17070,6 +17104,41 @@ fn reifyStruct( }; } + if (layout == .Packed) { + struct_obj.status = .layout_wip; + + for (struct_obj.fields.values()) |field, index| { + sema.resolveTypeLayout(block, src, field.ty) catch |err| switch (err) { + error.AnalysisFail => { + const msg = sema.err orelse return err; + try sema.addFieldErrNote(block, struct_ty, index, msg, "while checking this field", .{}); + return err; + }, + else => return err, + }; + } + + var fields_bit_sum: u64 = 0; + for (struct_obj.fields.values()) |field| { + fields_bit_sum += field.ty.bitSize(target); + } + + if (backing_int_val.optionalValue()) |payload| { + var buf: Value.ToTypeBuffer = undefined; + const backing_int_ty = payload.toType(&buf); + try sema.checkBackingIntType(block, src, backing_int_ty, fields_bit_sum); + struct_obj.backing_int_ty = try backing_int_ty.copy(new_decl_arena_allocator); + } else { + var buf: Type.Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = @intCast(u16, fields_bit_sum), + }; + struct_obj.backing_int_ty = try Type.initPayload(&buf.base).copy(new_decl_arena_allocator); + } + + struct_obj.status = .have_layout; + } + try new_decl.finalizeNewArena(&new_decl_arena); return sema.analyzeDeclVal(block, src, new_decl_index); } @@ -27154,6 +27223,11 @@ fn resolveStructLayout( else => return err, }; } + + if (struct_obj.layout == .Packed) { + try semaBackingIntType(sema.mod, struct_obj); + } + struct_obj.status = .have_layout; // In case of querying the ABI alignment of this struct, we will ask @@ -27173,6 +27247,109 @@ fn resolveStructLayout( // otherwise it's a tuple; no need to resolve anything } +fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!void { + const gpa = mod.gpa; + const target = mod.getTarget(); + + var fields_bit_sum: u64 = 0; + for (struct_obj.fields.values()) |field| { + fields_bit_sum += field.ty.bitSize(target); + } + + const decl_index = struct_obj.owner_decl; + const decl = mod.declPtr(decl_index); + var decl_arena = decl.value_arena.?.promote(gpa); + defer decl.value_arena.?.* = decl_arena.state; + const decl_arena_allocator = decl_arena.allocator(); + + const zir = struct_obj.namespace.file_scope.zir; + const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended; + assert(extended.opcode == .struct_decl); + const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); + + if (small.has_backing_int) { + var extra_index: usize = extended.operand; + extra_index += @boolToInt(small.has_src_node); + extra_index += @boolToInt(small.has_fields_len); + extra_index += @boolToInt(small.has_decls_len); + + const backing_int_body_len = zir.extra[extra_index]; + extra_index += 1; + + var analysis_arena = std.heap.ArenaAllocator.init(gpa); + defer analysis_arena.deinit(); + + var sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .perm_arena = decl_arena_allocator, + .code = zir, + .owner_decl = decl, + .owner_decl_index = decl_index, + .func = null, + .fn_ret_ty = Type.void, + .owner_func = null, + }; + defer sema.deinit(); + + var wip_captures = try WipCaptureScope.init(gpa, decl_arena_allocator, decl.src_scope); + defer wip_captures.deinit(); + + var block: Block = .{ + .parent = null, + .sema = &sema, + .src_decl = decl_index, + .namespace = &struct_obj.namespace, + .wip_capture_scope = wip_captures.scope, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer { + assert(block.instructions.items.len == 0); + block.params.deinit(gpa); + } + + const backing_int_src: LazySrcLoc = .{ .node_offset_container_tag = 0 }; + const backing_int_ty = blk: { + if (backing_int_body_len == 0) { + const backing_int_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); + break :blk try sema.resolveType(&block, backing_int_src, backing_int_ref); + } else { + const body = zir.extra[extra_index..][0..backing_int_body_len]; + const ty_ref = try sema.resolveBody(&block, body, struct_obj.zir_index); + break :blk try sema.analyzeAsType(&block, backing_int_src, ty_ref); + } + }; + + try sema.checkBackingIntType(&block, backing_int_src, backing_int_ty, fields_bit_sum); + struct_obj.backing_int_ty = try backing_int_ty.copy(decl_arena_allocator); + } else { + var buf: Type.Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = @intCast(u16, fields_bit_sum), + }; + struct_obj.backing_int_ty = try Type.initPayload(&buf.base).copy(decl_arena_allocator); + } +} + +fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_ty: Type, fields_bit_sum: u64) CompileError!void { + const target = sema.mod.getTarget(); + + if (!backing_int_ty.isInt()) { + return sema.fail(block, src, "expected backing integer type, found '{}'", .{backing_int_ty.fmt(sema.mod)}); + } + if (backing_int_ty.bitSize(target) != fields_bit_sum) { + return sema.fail( + block, + src, + "backing integer type '{}' has bit size {} but the struct fields have a total bit size of {}", + .{ backing_int_ty.fmt(sema.mod), backing_int_ty.bitSize(target), fields_bit_sum }, + ); + } +} + fn resolveUnionLayout( sema: *Sema, block: *Block, @@ -27495,12 +27672,26 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void break :decls_len decls_len; } else 0; + // The backing integer cannot be handled until `resolveStructLayout()`. + if (small.has_backing_int) { + const backing_int_body_len = zir.extra[extra_index]; + extra_index += 1; // backing_int_body_len + if (backing_int_body_len == 0) { + extra_index += 1; // backing_int_ref + } else { + extra_index += backing_int_body_len; // backing_int_body_inst + } + } + // Skip over decls. var decls_it = zir.declIteratorInner(extra_index, decls_len); while (decls_it.next()) |_| {} extra_index = decls_it.extra_index; if (fields_len == 0) { + if (struct_obj.layout == .Packed) { + try semaBackingIntType(mod, struct_obj); + } struct_obj.status = .have_layout; return; } diff --git a/src/Zir.zig b/src/Zir.zig index c62e6d02bb..538ef6aaf8 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -3085,13 +3085,16 @@ pub const Inst = struct { /// 0. src_node: i32, // if has_src_node /// 1. fields_len: u32, // if has_fields_len /// 2. decls_len: u32, // if has_decls_len - /// 3. decl_bits: u32 // for every 8 decls + /// 3. backing_int_body_len: u32, // if has_backing_int + /// 4. backing_int_ref: Ref, // if has_backing_int and backing_int_body_len is 0 + /// 5. backing_int_body_inst: Inst, // if has_backing_int and backing_int_body_len is > 0 + /// 6. decl_bits: u32 // for every 8 decls /// - sets of 4 bits: /// 0b000X: whether corresponding decl is pub /// 0b00X0: whether corresponding decl is exported /// 0b0X00: whether corresponding decl has an align expression /// 0bX000: whether corresponding decl has a linksection or an address space expression - /// 4. decl: { // for every decls_len + /// 7. decl: { // for every decls_len /// src_hash: [4]u32, // hash of source bytes /// line: u32, // line number of decl, relative to parent /// name: u32, // null terminated string index @@ -3109,13 +3112,13 @@ pub const Inst = struct { /// address_space: Ref, /// } /// } - /// 5. flags: u32 // for every 8 fields + /// 8. flags: u32 // for every 8 fields /// - sets of 4 bits: /// 0b000X: whether corresponding field has an align expression /// 0b00X0: whether corresponding field has a default expression /// 0b0X00: whether corresponding field is comptime /// 0bX000: whether corresponding field has a type expression - /// 6. fields: { // for every fields_len + /// 9. fields: { // for every fields_len /// field_name: u32, /// doc_comment: u32, // 0 if no doc comment /// field_type: Ref, // if corresponding bit is not set. none means anytype. @@ -3123,7 +3126,7 @@ pub const Inst = struct { /// align_body_len: u32, // if corresponding bit is set /// init_body_len: u32, // if corresponding bit is set /// } - /// 7. bodies: { // for every fields_len + /// 10. bodies: { // for every fields_len /// field_type_body_inst: Inst, // for each field_type_body_len /// align_body_inst: Inst, // for each align_body_len /// init_body_inst: Inst, // for each init_body_len @@ -3133,11 +3136,12 @@ pub const Inst = struct { has_src_node: bool, has_fields_len: bool, has_decls_len: bool, + has_backing_int: bool, known_non_opv: bool, known_comptime_only: bool, name_strategy: NameStrategy, layout: std.builtin.Type.ContainerLayout, - _: u7 = undefined, + _: u6 = undefined, }; }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 9c3efa18cd..6d2922ea3f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1683,8 +1683,7 @@ pub const Object = struct { if (ty.castTag(.@"struct")) |payload| { const struct_obj = payload.data; if (struct_obj.layout == .Packed) { - var buf: Type.Payload.Bits = undefined; - const info = struct_obj.packedIntegerType(target, &buf).intInfo(target); + const info = struct_obj.backing_int_ty.intInfo(target); const dwarf_encoding: c_uint = switch (info.signedness) { .signed => DW.ATE.signed, .unsigned => DW.ATE.unsigned, @@ -2679,9 +2678,7 @@ pub const DeclGen = struct { const struct_obj = t.castTag(.@"struct").?.data; if (struct_obj.layout == .Packed) { - var buf: Type.Payload.Bits = undefined; - const int_ty = struct_obj.packedIntegerType(target, &buf); - const int_llvm_ty = try dg.lowerType(int_ty); + const int_llvm_ty = try dg.lowerType(struct_obj.backing_int_ty); gop.value_ptr.* = int_llvm_ty; return int_llvm_ty; } @@ -3330,8 +3327,8 @@ pub const DeclGen = struct { const struct_obj = tv.ty.castTag(.@"struct").?.data; if (struct_obj.layout == .Packed) { - const big_bits = struct_obj.packedIntegerBits(target); - const int_llvm_ty = dg.context.intType(big_bits); + const big_bits = struct_obj.backing_int_ty.bitSize(target); + const int_llvm_ty = dg.context.intType(@intCast(c_uint, big_bits)); const fields = struct_obj.fields.values(); comptime assert(Type.packed_struct_layout_version == 2); var running_int: *const llvm.Value = int_llvm_ty.constNull(); @@ -8243,8 +8240,8 @@ pub const FuncGen = struct { .Struct => { if (result_ty.containerLayout() == .Packed) { const struct_obj = result_ty.castTag(.@"struct").?.data; - const big_bits = struct_obj.packedIntegerBits(target); - const int_llvm_ty = self.dg.context.intType(big_bits); + const big_bits = struct_obj.backing_int_ty.bitSize(target); + const int_llvm_ty = self.dg.context.intType(@intCast(c_uint, big_bits)); const fields = struct_obj.fields.values(); comptime assert(Type.packed_struct_layout_version == 2); var running_int: *const llvm.Value = int_llvm_ty.constNull(); diff --git a/src/print_zir.zig b/src/print_zir.zig index 2b70799c36..4bc96c4259 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -1245,9 +1245,28 @@ const Writer = struct { try self.writeFlag(stream, "known_non_opv, ", small.known_non_opv); try self.writeFlag(stream, "known_comptime_only, ", small.known_comptime_only); - try stream.print("{s}, {s}, ", .{ - @tagName(small.name_strategy), @tagName(small.layout), - }); + + try stream.print("{s}, ", .{@tagName(small.name_strategy)}); + + if (small.layout == .Packed and small.has_backing_int) { + const backing_int_body_len = self.code.extra[extra_index]; + extra_index += 1; + try stream.writeAll("Packed("); + if (backing_int_body_len == 0) { + const backing_int_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]); + extra_index += 1; + try self.writeInstRef(stream, backing_int_ref); + } else { + const body = self.code.extra[extra_index..][0..backing_int_body_len]; + extra_index += backing_int_body_len; + self.indent += 2; + try self.writeBracedDecl(stream, body); + self.indent -= 2; + } + try stream.writeAll("), "); + } else { + try stream.print("{s}, ", .{@tagName(small.layout)}); + } if (decls_len == 0) { try stream.writeAll("{}, "); diff --git a/src/stage1/all_types.hpp b/src/stage1/all_types.hpp index 4028c3872d..9f9a6151b8 100644 --- a/src/stage1/all_types.hpp +++ b/src/stage1/all_types.hpp @@ -1116,6 +1116,7 @@ struct AstNodeContainerDecl { ContainerLayout layout; bool auto_enum, is_root; // union(enum) + bool unsupported_explicit_backing_int; }; struct AstNodeErrorSetField { diff --git a/src/stage1/analyze.cpp b/src/stage1/analyze.cpp index 08aa8bbf06..90173f384e 100644 --- a/src/stage1/analyze.cpp +++ b/src/stage1/analyze.cpp @@ -3034,6 +3034,12 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) { AstNode *decl_node = struct_type->data.structure.decl_node; + if (decl_node->data.container_decl.unsupported_explicit_backing_int) { + add_node_error(g, decl_node, buf_create_from_str( + "the stage1 compiler does not support explicit backing integer types on packed structs")); + return ErrorSemanticAnalyzeFail; + } + if (struct_type->data.structure.resolve_loop_flag_zero_bits) { if (struct_type->data.structure.resolve_status != ResolveStatusInvalid) { struct_type->data.structure.resolve_status = ResolveStatusInvalid; diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index e31715030c..a5428945a9 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -18640,7 +18640,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour result->special = ConstValSpecialStatic; result->type = ir_type_info_get_type(ira, "Struct", nullptr); - ZigValue **fields = alloc_const_vals_ptrs(g, 4); + ZigValue **fields = alloc_const_vals_ptrs(g, 5); result->data.x_struct.fields = fields; // layout: ContainerLayout @@ -18648,8 +18648,17 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour fields[0]->special = ConstValSpecialStatic; fields[0]->type = ir_type_info_get_type(ira, "ContainerLayout", nullptr); bigint_init_unsigned(&fields[0]->data.x_enum_tag, type_entry->data.structure.layout); + + // backing_integer: ?type + ensure_field_index(result->type, "backing_integer", 1); + fields[1]->special = ConstValSpecialStatic; + fields[1]->type = get_optional_type(g, g->builtin_types.entry_type); + // This is always null in stage1, as stage1 does not support explicit backing integers + // for packed structs. + fields[1]->data.x_optional = nullptr; + // fields: []Type.StructField - ensure_field_index(result->type, "fields", 1); + ensure_field_index(result->type, "fields", 2); ZigType *type_info_struct_field_type = ir_type_info_get_type(ira, "StructField", nullptr); if ((err = type_resolve(g, type_info_struct_field_type, ResolveStatusSizeKnown))) { @@ -18663,7 +18672,7 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour struct_field_array->data.x_array.special = ConstArraySpecialNone; struct_field_array->data.x_array.data.s_none.elements = g->pass1_arena->allocate(struct_field_count); - init_const_slice(g, fields[1], struct_field_array, 0, struct_field_count, false, nullptr); + init_const_slice(g, fields[2], struct_field_array, 0, struct_field_count, false, nullptr); for (uint32_t struct_field_index = 0; struct_field_index < struct_field_count; struct_field_index++) { TypeStructField *struct_field = type_entry->data.structure.fields[struct_field_index]; @@ -18710,18 +18719,18 @@ static Error ir_make_type_info_value(IrAnalyze *ira, Scope *scope, AstNode *sour struct_field_val->parent.data.p_array.elem_index = struct_field_index; } // decls: []Type.Declaration - ensure_field_index(result->type, "decls", 2); - if ((err = ir_make_type_info_decls(ira, source_node, fields[2], + ensure_field_index(result->type, "decls", 3); + if ((err = ir_make_type_info_decls(ira, source_node, fields[3], type_entry->data.structure.decls_scope, false))) { return err; } // is_tuple: bool - ensure_field_index(result->type, "is_tuple", 3); - fields[3]->special = ConstValSpecialStatic; - fields[3]->type = g->builtin_types.entry_bool; - fields[3]->data.x_bool = is_tuple(type_entry); + ensure_field_index(result->type, "is_tuple", 4); + fields[4]->special = ConstValSpecialStatic; + fields[4]->type = g->builtin_types.entry_bool; + fields[4]->data.x_bool = is_tuple(type_entry); break; } @@ -19313,7 +19322,14 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ assert(layout_value->type == ir_type_info_get_type(ira, "ContainerLayout", nullptr)); ContainerLayout layout = (ContainerLayout)bigint_as_u32(&layout_value->data.x_enum_tag); - ZigValue *fields_value = get_const_field(ira, source_node, payload, "fields", 1); + ZigType *tag_type = get_const_field_meta_type_optional(ira, source_node, payload, "backing_integer", 1); + if (tag_type != nullptr) { + ir_add_error_node(ira, source_node, buf_create_from_str( + "the stage1 compiler does not support explicit backing integer types on packed structs")); + return ira->codegen->invalid_inst_gen->value->type; + } + + ZigValue *fields_value = get_const_field(ira, source_node, payload, "fields", 2); if (fields_value == nullptr) return ira->codegen->invalid_inst_gen->value->type; assert(fields_value->special == ConstValSpecialStatic); @@ -19322,7 +19338,7 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ ZigValue *fields_len_value = fields_value->data.x_struct.fields[slice_len_index]; size_t fields_len = bigint_as_usize(&fields_len_value->data.x_bigint); - ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 2); + ZigValue *decls_value = get_const_field(ira, source_node, payload, "decls", 3); if (decls_value == nullptr) return ira->codegen->invalid_inst_gen->value->type; assert(decls_value->special == ConstValSpecialStatic); @@ -19335,7 +19351,7 @@ static ZigType *type_info_to_type(IrAnalyze *ira, Scope *scope, AstNode *source_ } bool is_tuple; - if ((err = get_const_field_bool(ira, source_node, payload, "is_tuple", 3, &is_tuple))) + if ((err = get_const_field_bool(ira, source_node, payload, "is_tuple", 4, &is_tuple))) return ira->codegen->invalid_inst_gen->value->type; ZigType *entry = new_type_table_entry(ZigTypeIdStruct); diff --git a/src/stage1/parser.cpp b/src/stage1/parser.cpp index fdc0777aff..bd778484cb 100644 --- a/src/stage1/parser.cpp +++ b/src/stage1/parser.cpp @@ -2902,16 +2902,25 @@ static AstNode *ast_parse_container_decl_auto(ParseContext *pc) { } // ContainerDeclType -// <- KEYWORD_struct +// <- KEYWORD_struct (LPAREN Expr RPAREN)? // / KEYWORD_enum (LPAREN Expr RPAREN)? // / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)? // / KEYWORD_opaque static AstNode *ast_parse_container_decl_type(ParseContext *pc) { TokenIndex first = eat_token_if(pc, TokenIdKeywordStruct); if (first != 0) { + bool explicit_backing_int = false; + if (eat_token_if(pc, TokenIdLParen) != 0) { + explicit_backing_int = true; + ast_expect(pc, ast_parse_expr); + expect_token(pc, TokenIdRParen); + } AstNode *res = ast_create_node(pc, NodeTypeContainerDecl, first); res->data.container_decl.init_arg_expr = nullptr; res->data.container_decl.kind = ContainerKindStruct; + // We want this to be an error in semantic analysis not parsing to make sharing + // the test suite between stage1 and self hosted easier. + res->data.container_decl.unsupported_explicit_backing_int = explicit_backing_int; return res; } diff --git a/src/type.zig b/src/type.zig index 85d77303c9..1b71f4e9b1 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3000,9 +3000,17 @@ pub const Type = extern union { .lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) }, }; if (struct_obj.layout == .Packed) { - var buf: Type.Payload.Bits = undefined; - const int_ty = struct_obj.packedIntegerType(target, &buf); - return AbiAlignmentAdvanced{ .scalar = int_ty.abiAlignment(target) }; + switch (strat) { + .sema_kit => |sk| try sk.sema.resolveTypeLayout(sk.block, sk.src, ty), + .lazy => |arena| { + if (!struct_obj.haveLayout()) { + return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) }; + } + }, + .eager => {}, + } + assert(struct_obj.haveLayout()); + return AbiAlignmentAdvanced{ .scalar = struct_obj.backing_int_ty.abiAlignment(target) }; } const fields = ty.structFields(); @@ -3192,17 +3200,16 @@ pub const Type = extern union { .Packed => { const struct_obj = ty.castTag(.@"struct").?.data; switch (strat) { - .sema_kit => |sk| _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty), + .sema_kit => |sk| try sk.sema.resolveTypeLayout(sk.block, sk.src, ty), .lazy => |arena| { - if (!struct_obj.haveFieldTypes()) { + if (!struct_obj.haveLayout()) { return AbiSizeAdvanced{ .val = try Value.Tag.lazy_size.create(arena, ty) }; } }, .eager => {}, } - var buf: Type.Payload.Bits = undefined; - const int_ty = struct_obj.packedIntegerType(target, &buf); - return AbiSizeAdvanced{ .scalar = int_ty.abiSize(target) }; + assert(struct_obj.haveLayout()); + return AbiSizeAdvanced{ .scalar = struct_obj.backing_int_ty.abiSize(target) }; }, else => { switch (strat) { diff --git a/test/behavior.zig b/test/behavior.zig index fee61f5e09..e9c4ec779b 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -165,6 +165,7 @@ test { if (builtin.zig_backend != .stage1) { _ = @import("behavior/decltest.zig"); + _ = @import("behavior/packed_struct_explicit_backing_int.zig"); } if (builtin.os.tag != .wasi) { diff --git a/test/behavior/packed_struct_explicit_backing_int.zig b/test/behavior/packed_struct_explicit_backing_int.zig new file mode 100644 index 0000000000..165e94fd4e --- /dev/null +++ b/test/behavior/packed_struct_explicit_backing_int.zig @@ -0,0 +1,53 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const assert = std.debug.assert; +const expectEqual = std.testing.expectEqual; +const native_endian = builtin.cpu.arch.endian(); + +test "packed struct explicit backing integer" { + assert(builtin.zig_backend != .stage1); + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const S1 = packed struct { a: u8, b: u8, c: u8 }; + + const S2 = packed struct(i24) { d: u8, e: u8, f: u8 }; + + const S3 = packed struct { x: S1, y: S2 }; + const S3Padded = packed struct(u64) { s3: S3, pad: u16 }; + + try expectEqual(48, @bitSizeOf(S3)); + try expectEqual(@sizeOf(u48), @sizeOf(S3)); + + try expectEqual(3, @offsetOf(S3, "y")); + try expectEqual(24, @bitOffsetOf(S3, "y")); + + if (native_endian == .Little) { + const s3 = @bitCast(S3Padded, @as(u64, 0xe952d5c71ff4)).s3; + try expectEqual(@as(u8, 0xf4), s3.x.a); + try expectEqual(@as(u8, 0x1f), s3.x.b); + try expectEqual(@as(u8, 0xc7), s3.x.c); + try expectEqual(@as(u8, 0xd5), s3.y.d); + try expectEqual(@as(u8, 0x52), s3.y.e); + try expectEqual(@as(u8, 0xe9), s3.y.f); + } + + const S4 = packed struct { a: i32, b: i8 }; + const S5 = packed struct(u80) { a: i32, b: i8, c: S4 }; + const S6 = packed struct(i80) { a: i32, b: S4, c: i8 }; + + const expectedBitSize = 80; + const expectedByteSize = @sizeOf(u80); + try expectEqual(expectedBitSize, @bitSizeOf(S5)); + try expectEqual(expectedByteSize, @sizeOf(S5)); + try expectEqual(expectedBitSize, @bitSizeOf(S6)); + try expectEqual(expectedByteSize, @sizeOf(S6)); + + try expectEqual(5, @offsetOf(S5, "c")); + try expectEqual(40, @bitOffsetOf(S5, "c")); + try expectEqual(9, @offsetOf(S6, "c")); + try expectEqual(72, @bitOffsetOf(S6, "c")); +} diff --git a/test/behavior/type_info.zig b/test/behavior/type_info.zig index b1012e69c8..968c3e7490 100644 --- a/test/behavior/type_info.zig +++ b/test/behavior/type_info.zig @@ -293,6 +293,7 @@ test "type info: struct info" { fn testStruct() !void { const unpacked_struct_info = @typeInfo(TestStruct); try expect(unpacked_struct_info.Struct.is_tuple == false); + try expect(unpacked_struct_info.Struct.backing_integer == null); try expect(unpacked_struct_info.Struct.fields[0].alignment == @alignOf(u32)); try expect(@ptrCast(*const u32, unpacked_struct_info.Struct.fields[0].default_value.?).* == 4); try expect(mem.eql(u8, "foobar", @ptrCast(*const *const [6:0]u8, unpacked_struct_info.Struct.fields[1].default_value.?).*)); @@ -315,6 +316,7 @@ fn testPackedStruct() !void { try expect(struct_info == .Struct); try expect(struct_info.Struct.is_tuple == false); try expect(struct_info.Struct.layout == .Packed); + try expect(struct_info.Struct.backing_integer == u128); try expect(struct_info.Struct.fields.len == 4); try expect(struct_info.Struct.fields[0].alignment == 0); try expect(struct_info.Struct.fields[2].field_type == f32); @@ -326,7 +328,7 @@ fn testPackedStruct() !void { } const TestPackedStruct = packed struct { - fieldA: usize, + fieldA: u64, fieldB: void, fieldC: f32, fieldD: u32 = 4, diff --git a/test/cases/compile_errors/packed_struct_backing_int_wrong.zig b/test/cases/compile_errors/packed_struct_backing_int_wrong.zig new file mode 100644 index 0000000000..cd1b4ec11c --- /dev/null +++ b/test/cases/compile_errors/packed_struct_backing_int_wrong.zig @@ -0,0 +1,55 @@ +export fn entry1() void { + _ = @sizeOf(packed struct(u32) { + x: u1, + y: u24, + z: u4, + }); +} +export fn entry2() void { + _ = @sizeOf(packed struct(i31) { + x: u4, + y: u24, + z: u4, + }); +} + +export fn entry3() void { + _ = @sizeOf(packed struct(void) { + x: void, + }); +} + +export fn entry4() void { + _ = @sizeOf(packed struct(void) {}); +} + +export fn entry5() void { + _ = @sizeOf(packed struct(noreturn) {}); +} + +export fn entry6() void { + _ = @sizeOf(packed struct(f64) { + x: u32, + y: f32, + }); +} + +export fn entry7() void { + _ = @sizeOf(packed struct(*u32) { + x: u4, + y: u24, + z: u4, + }); +} + +// error +// backend=llvm +// target=native +// +// :2:31: error: backing integer type 'u32' has bit size 32 but the struct fields have a total bit size of 29 +// :9:31: error: backing integer type 'i31' has bit size 31 but the struct fields have a total bit size of 32 +// :17:31: error: expected backing integer type, found 'void' +// :23:31: error: expected backing integer type, found 'void' +// :27:31: error: expected backing integer type, found 'noreturn' +// :31:31: error: expected backing integer type, found 'f64' +// :38:31: error: expected backing integer type, found '*u32' -- cgit v1.2.3 From d267f0f968c97d77d8434ef9918c71fb4c11f45d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 10 Aug 2022 14:08:09 -0700 Subject: LLVM: respect linksection for exported variables --- src/Sema.zig | 1 + src/codegen/llvm.zig | 11 +++++++++++ src/codegen/llvm/bindings.zig | 3 +++ 3 files changed, 15 insertions(+) (limited to 'src/codegen/llvm.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 9aaa761a03..f9b37f5b49 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6351,6 +6351,7 @@ fn instantiateGenericCall( new_decl.is_exported = fn_owner_decl.is_exported; new_decl.has_align = fn_owner_decl.has_align; new_decl.has_linksection_or_addrspace = fn_owner_decl.has_linksection_or_addrspace; + new_decl.@"linksection" = fn_owner_decl.@"linksection"; new_decl.@"addrspace" = fn_owner_decl.@"addrspace"; new_decl.zir_decl_index = fn_owner_decl.zir_decl_index; new_decl.alive = true; // This Decl is called at runtime. diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 6d2922ea3f..aa846f4247 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -722,6 +722,10 @@ pub const Object = struct { dg.addFnAttrString(llvm_func, "no-stack-arg-probe", ""); } + if (decl.@"linksection") |section| { + llvm_func.setSection(section); + } + // Remove all the basic blocks of a function in order to start over, generating // LLVM IR from an empty function body. while (llvm_func.getFirstBasicBlock()) |bb| { @@ -1107,6 +1111,11 @@ pub const Object = struct { .hidden => llvm_global.setVisibility(.Hidden), .protected => llvm_global.setVisibility(.Protected), } + if (exports[0].options.section) |section| { + const section_z = try module.gpa.dupeZ(u8, section); + defer module.gpa.free(section_z); + llvm_global.setSection(section_z); + } if (decl.val.castTag(.variable)) |variable| { if (variable.data.is_threadlocal) { llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel); @@ -2183,6 +2192,7 @@ pub const DeclGen = struct { const target = dg.module.getTarget(); var global = try dg.resolveGlobalDecl(decl_index); global.setAlignment(decl.getAlignment(target)); + if (decl.@"linksection") |section| global.setSection(section); assert(decl.has_tv); const init_val = if (decl.val.castTag(.variable)) |payload| init_val: { const variable = payload.data; @@ -2216,6 +2226,7 @@ pub const DeclGen = struct { new_global.setLinkage(global.getLinkage()); new_global.setUnnamedAddr(global.getUnnamedAddress()); new_global.setAlignment(global.getAlignment()); + if (decl.@"linksection") |section| new_global.setSection(section); new_global.setInitializer(llvm_init); // replaceAllUsesWith requires the type to be unchanged. So we bitcast // the new global to the old type and use that as the thing to replace diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index e4357b8060..baf67b15aa 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -126,6 +126,9 @@ pub const Value = opaque { pub const setThreadLocalMode = LLVMSetThreadLocalMode; extern fn LLVMSetThreadLocalMode(Global: *const Value, Mode: ThreadLocalMode) void; + pub const setSection = LLVMSetSection; + extern fn LLVMSetSection(Global: *const Value, Section: [*:0]const u8) void; + pub const deleteGlobal = LLVMDeleteGlobal; extern fn LLVMDeleteGlobal(GlobalVar: *const Value) void; -- cgit v1.2.3 From 74673b7f69b27dc39a653f92eb58bba71e289f39 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 10 Aug 2022 22:45:46 +0300 Subject: stage2 llvm: implement more C ABI --- src/codegen/llvm.zig | 124 +++++++++++++++++++++++++++++++++++++++--- src/codegen/llvm/bindings.zig | 3 + test/standalone.zig | 2 +- 3 files changed, 119 insertions(+), 10 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index aa846f4247..38f7d285ae 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -921,6 +921,40 @@ pub const Object = struct { }; try args.append(loaded); }, + .multiple_llvm_float => { + const llvm_floats = it.llvm_types_buffer[0..it.llvm_types_len]; + const param_ty = fn_info.param_types[it.zig_index - 1]; + const param_llvm_ty = try dg.lowerType(param_ty); + const param_alignment = param_ty.abiAlignment(target); + const arg_ptr = buildAllocaInner(builder, llvm_func, false, param_llvm_ty); + arg_ptr.setAlignment(param_alignment); + var field_types_buf: [8]*const llvm.Type = undefined; + const field_types = field_types_buf[0..llvm_floats.len]; + for (llvm_floats) |float_bits, i| { + switch (float_bits) { + 64 => field_types[i] = dg.context.doubleType(), + 80 => field_types[i] = dg.context.x86FP80Type(), + else => {}, + } + } + const ints_llvm_ty = dg.context.structType(field_types.ptr, @intCast(c_uint, field_types.len), .False); + const casted_ptr = builder.buildBitCast(arg_ptr, ints_llvm_ty.pointerType(0), ""); + for (llvm_floats) |_, i_usize| { + const i = @intCast(c_uint, i_usize); + const param = llvm_func.getParam(i); + const field_ptr = builder.buildStructGEP(casted_ptr, i, ""); + const store_inst = builder.buildStore(param, field_ptr); + store_inst.setAlignment(target.cpu.arch.ptrBitWidth() / 8); + } + + const is_by_ref = isByRef(param_ty); + const loaded = if (is_by_ref) arg_ptr else l: { + const load_inst = builder.buildLoad(arg_ptr, ""); + load_inst.setAlignment(param_alignment); + break :l load_inst; + }; + try args.append(loaded); + }, .as_u16 => { const param = llvm_func.getParam(llvm_arg_i); llvm_arg_i += 1; @@ -2341,6 +2375,14 @@ pub const DeclGen = struct { dg.addFnAttr(llvm_fn, "noreturn"); } + var llvm_arg_i = @as(c_uint, @boolToInt(sret)) + @boolToInt(err_return_tracing); + var it = iterateParamTypes(dg, fn_info); + while (it.next()) |_| : (llvm_arg_i += 1) { + if (!it.byval_attr) continue; + const param = llvm_fn.getParam(llvm_arg_i); + llvm_fn.addByValAttr(llvm_arg_i, param.typeOf().getElementType()); + } + return llvm_fn; } @@ -2894,6 +2936,18 @@ pub const DeclGen = struct { llvm_params.appendAssumeCapacity(big_int_ty); } }, + .multiple_llvm_float => { + const llvm_ints = it.llvm_types_buffer[0..it.llvm_types_len]; + try llvm_params.ensureUnusedCapacity(it.llvm_types_len); + for (llvm_ints) |float_bits| { + const float_ty = switch (float_bits) { + 64 => dg.context.doubleType(), + 80 => dg.context.x86FP80Type(), + else => unreachable, + }; + llvm_params.appendAssumeCapacity(float_ty); + } + }, .as_u16 => { try llvm_params.append(dg.context.intType(16)); }, @@ -4402,6 +4456,39 @@ pub const FuncGen = struct { llvm_args.appendAssumeCapacity(load_inst); } }, + .multiple_llvm_float => { + const arg = args[it.zig_index - 1]; + const param_ty = self.air.typeOf(arg); + const llvm_floats = it.llvm_types_buffer[0..it.llvm_types_len]; + const llvm_arg = try self.resolveInst(arg); + const is_by_ref = isByRef(param_ty); + const arg_ptr = if (is_by_ref) llvm_arg else p: { + const p = self.buildAlloca(llvm_arg.typeOf()); + const store_inst = self.builder.buildStore(llvm_arg, p); + store_inst.setAlignment(param_ty.abiAlignment(target)); + break :p p; + }; + + var field_types_buf: [8]*const llvm.Type = undefined; + const field_types = field_types_buf[0..llvm_floats.len]; + for (llvm_floats) |float_bits, i| { + switch (float_bits) { + 64 => field_types[i] = self.dg.context.doubleType(), + 80 => field_types[i] = self.dg.context.x86FP80Type(), + else => {}, + } + } + const ints_llvm_ty = self.dg.context.structType(field_types.ptr, @intCast(c_uint, field_types.len), .False); + const casted_ptr = self.builder.buildBitCast(arg_ptr, ints_llvm_ty.pointerType(0), ""); + try llvm_args.ensureUnusedCapacity(it.llvm_types_len); + for (llvm_floats) |_, i_usize| { + const i = @intCast(c_uint, i_usize); + const field_ptr = self.builder.buildStructGEP(casted_ptr, i, ""); + const load_inst = self.builder.buildLoad(field_ptr, ""); + load_inst.setAlignment(target.cpu.arch.ptrBitWidth() / 8); + llvm_args.appendAssumeCapacity(load_inst); + } + }, .as_u16 => { const arg = args[it.zig_index - 1]; const llvm_arg = try self.resolveInst(arg); @@ -9367,16 +9454,20 @@ fn lowerFnRetTy(dg: *DeclGen, fn_info: Type.Payload.Function.Data) !*const llvm. llvm_types_index += 1; }, .sse => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = dg.context.doubleType(); + llvm_types_index += 1; }, .sseup => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = dg.context.doubleType(); + llvm_types_index += 1; }, .x87 => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = dg.context.x86FP80Type(); + llvm_types_index += 1; }, .x87up => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = dg.context.x86FP80Type(); + llvm_types_index += 1; }, .complex_x87 => { @panic("TODO"); @@ -9422,6 +9513,7 @@ const ParamTypeIterator = struct { target: std.Target, llvm_types_len: u32, llvm_types_buffer: [8]u16, + byval_attr: bool, const Lowering = enum { no_bits, @@ -9429,6 +9521,7 @@ const ParamTypeIterator = struct { byref, abi_sized_int, multiple_llvm_ints, + multiple_llvm_float, slice, as_u16, }; @@ -9436,6 +9529,7 @@ const ParamTypeIterator = struct { pub fn next(it: *ParamTypeIterator) ?Lowering { if (it.zig_index >= it.fn_info.param_types.len) return null; const ty = it.fn_info.param_types[it.zig_index]; + it.byval_attr = false; return nextInner(it, ty); } @@ -9521,6 +9615,7 @@ const ParamTypeIterator = struct { .memory => { it.zig_index += 1; it.llvm_index += 1; + it.byval_attr = true; return .byref; }, .sse => { @@ -9540,6 +9635,7 @@ const ParamTypeIterator = struct { if (classes[0] == .memory) { it.zig_index += 1; it.llvm_index += 1; + it.byval_attr = true; return .byref; } var llvm_types_buffer: [8]u16 = undefined; @@ -9551,16 +9647,20 @@ const ParamTypeIterator = struct { llvm_types_index += 1; }, .sse => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = 64; + llvm_types_index += 1; }, .sseup => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = 64; + llvm_types_index += 1; }, .x87 => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = 80; + llvm_types_index += 1; }, .x87up => { - @panic("TODO"); + llvm_types_buffer[llvm_types_index] = 80; + llvm_types_index += 1; }, .complex_x87 => { @panic("TODO"); @@ -9574,11 +9674,16 @@ const ParamTypeIterator = struct { it.llvm_index += 1; return .abi_sized_int; } + if (classes[0] == .sse and classes[1] == .none) { + it.zig_index += 1; + it.llvm_index += 1; + return .byval; + } it.llvm_types_buffer = llvm_types_buffer; it.llvm_types_len = llvm_types_index; it.llvm_index += llvm_types_index; it.zig_index += 1; - return .multiple_llvm_ints; + return if (classes[0] == .integer) .multiple_llvm_ints else .multiple_llvm_float; }, }, .wasm32 => { @@ -9619,6 +9724,7 @@ fn iterateParamTypes(dg: *DeclGen, fn_info: Type.Payload.Function.Data) ParamTyp .target = dg.module.getTarget(), .llvm_types_buffer = undefined, .llvm_types_len = 0, + .byval_attr = false, }; } diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index baf67b15aa..9daa96eb8f 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -248,6 +248,9 @@ pub const Value = opaque { pub const addFunctionAttr = ZigLLVMAddFunctionAttr; extern fn ZigLLVMAddFunctionAttr(Fn: *const Value, attr_name: [*:0]const u8, attr_value: [*:0]const u8) void; + + pub const addByValAttr = ZigLLVMAddByValAttr; + extern fn ZigLLVMAddByValAttr(Fn: *const Value, ArgNo: c_uint, type: *const Type) void; }; pub const Type = opaque { diff --git a/test/standalone.zig b/test/standalone.zig index 76e46a1b62..2b0667e89c 100644 --- a/test/standalone.zig +++ b/test/standalone.zig @@ -40,7 +40,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void { } // C ABI compatibility issue: https://github.com/ziglang/zig/issues/1481 if (builtin.cpu.arch == .x86_64) { - if (builtin.zig_backend == .stage1) { // https://github.com/ziglang/zig/issues/12222 + if (builtin.zig_backend == .stage1 or builtin.zig_backend == .stage2_llvm) { // https://github.com/ziglang/zig/issues/12222 cases.addBuildFile("test/c_abi/build.zig", .{}); } } -- cgit v1.2.3 From 7c9979a02e830a4383995e66ff623a7d07cac091 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 11 Aug 2022 22:45:15 +0300 Subject: stage2: generate a switch for `@errSetCast` safety --- src/Air.zig | 5 +++++ src/Liveness.zig | 2 ++ src/Sema.zig | 13 +++--------- src/arch/aarch64/CodeGen.zig | 1 + src/arch/arm/CodeGen.zig | 1 + src/arch/riscv64/CodeGen.zig | 1 + src/arch/sparc64/CodeGen.zig | 1 + src/arch/wasm/CodeGen.zig | 1 + src/arch/x86_64/CodeGen.zig | 1 + src/codegen/c.zig | 1 + src/codegen/llvm.zig | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/print_air.zig | 1 + 12 files changed, 66 insertions(+), 10 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Air.zig b/src/Air.zig index c5734278d3..e08993bbed 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -673,6 +673,10 @@ pub const Inst = struct { /// Uses the `un_op` field. error_name, + /// Returns true if error set has error with value. + /// Uses the `ty_op` field. + error_set_has_value, + /// Constructs a vector, tuple, struct, or array value out of runtime-known elements. /// Some of the elements may be comptime-known. /// Uses the `ty_pl` field, payload is index of an array of elements, each of which @@ -1062,6 +1066,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .is_err_ptr, .is_non_err_ptr, .is_named_enum_value, + .error_set_has_value, => return Type.bool, .const_ty => return Type.type, diff --git a/src/Liveness.zig b/src/Liveness.zig index 748016d584..5a4bd2265e 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -267,6 +267,7 @@ pub fn categorizeOperand( .byte_swap, .bit_reverse, .splat, + .error_set_has_value, => { const o = air_datas[inst].ty_op; if (o.operand == operand_ref) return matchOperandSmallIndex(l, inst, 0, .none); @@ -842,6 +843,7 @@ fn analyzeInst( .byte_swap, .bit_reverse, .splat, + .error_set_has_value, => { const o = inst_datas[inst].ty_op; return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none }); diff --git a/src/Sema.zig b/src/Sema.zig index f9b37f5b49..3e62344a04 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -17359,17 +17359,10 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat } try sema.requireRuntimeBlock(block, src, operand_src); - if (block.wantSafety() and !dest_ty.isAnyError()) { + if (block.wantSafety() and !dest_ty.isAnyError() and sema.mod.comp.bin_file.options.use_llvm) { const err_int_inst = try block.addBitCast(Type.u16, operand); - // TODO: Output a switch instead of chained OR's. - var found_match: Air.Inst.Ref = undefined; - for (dest_ty.errorSetNames()) |dest_err_name, i| { - const dest_err_int = (try sema.mod.getErrorValue(dest_err_name)).value; - const dest_err_int_inst = try sema.addIntUnsigned(Type.u16, dest_err_int); - const next_match = try block.addBinOp(.cmp_eq, dest_err_int_inst, err_int_inst); - found_match = if (i == 0) next_match else try block.addBinOp(.bool_or, found_match, next_match); - } - try sema.addSafetyCheck(block, found_match, .invalid_error_code); + const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst); + try sema.addSafetyCheck(block, ok, .invalid_error_code); } return block.addBitCast(dest_ty, operand); } diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index 988317284b..d256f9a558 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -778,6 +778,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { => return self.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}), .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index be2891ef6f..60008ec1d1 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -769,6 +769,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { => return self.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}), .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index a5007928b6..06adcff6d4 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -694,6 +694,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { => return self.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}), .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, diff --git a/src/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index bf834a36d9..cd891f0fa3 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -706,6 +706,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { => @panic("TODO implement optimized float mode"), .is_named_enum_value => @panic("TODO implement is_named_enum_value"), + .error_set_has_value => @panic("TODO implement error_set_has_value"), .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 53b7b7fb6b..8eb3e2175b 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1694,6 +1694,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { .err_return_trace, .set_err_return_trace, .is_named_enum_value, + .error_set_has_value, => |tag| return self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}), .add_optimized, diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 7f7473bc66..8d19768bbb 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -776,6 +776,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { => return self.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return self.fail("TODO implement is_named_enum_value", .{}), + .error_set_has_value => return self.fail("TODO implement error_set_has_value", .{}), .wasm_memory_size => unreachable, .wasm_memory_grow => unreachable, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 74e4404bce..81a892183f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1954,6 +1954,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO => return f.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), + .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}), // zig fmt: on }; switch (result_value) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 38f7d285ae..5da3e7e327 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4247,6 +4247,7 @@ pub const FuncGen = struct { .prefetch => try self.airPrefetch(inst), .is_named_enum_value => try self.airIsNamedEnumValue(inst), + .error_set_has_value => try self.airErrorSetHasValue(inst), .reduce => try self.airReduce(inst, false), .reduce_optimized => try self.airReduce(inst, true), @@ -7983,6 +7984,53 @@ pub const FuncGen = struct { } } + fn airErrorSetHasValue(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 error_set_ty = self.air.getRefType(ty_op.ty); + + const names = error_set_ty.errorSetNames(); + const valid_block = self.dg.context.appendBasicBlock(self.llvm_func, "Valid"); + const invalid_block = self.dg.context.appendBasicBlock(self.llvm_func, "Invalid"); + const end_block = self.context.appendBasicBlock(self.llvm_func, "End"); + const switch_instr = self.builder.buildSwitch(operand, invalid_block, @intCast(c_uint, names.len)); + + for (names) |name| { + const err_int = self.dg.module.global_error_set.get(name).?; + const this_tag_int_value = int: { + var tag_val_payload: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = err_int, + }; + break :int try self.dg.lowerValue(.{ + .ty = Type.u16, + .val = Value.initPayload(&tag_val_payload.base), + }); + }; + switch_instr.addCase(this_tag_int_value, valid_block); + } + self.builder.positionBuilderAtEnd(valid_block); + _ = self.builder.buildBr(end_block); + + self.builder.positionBuilderAtEnd(invalid_block); + _ = self.builder.buildBr(end_block); + + self.builder.positionBuilderAtEnd(end_block); + + const llvm_type = self.dg.context.intType(1); + const incoming_values: [2]*const llvm.Value = .{ + llvm_type.constInt(1, .False), llvm_type.constInt(0, .False), + }; + const incoming_blocks: [2]*const llvm.BasicBlock = .{ + valid_block, invalid_block, + }; + const phi_node = self.builder.buildPhi(llvm_type, ""); + phi_node.addIncoming(&incoming_values, &incoming_blocks, 2); + return phi_node; + } + fn airIsNamedEnumValue(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index 23107946f6..04dec25f5f 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -243,6 +243,7 @@ const Writer = struct { .popcount, .byte_swap, .bit_reverse, + .error_set_has_value, => try w.writeTyOp(s, inst), .block, -- cgit v1.2.3 From 09f273136c74639d18695f63ba9c0fdc68ad678c Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 11 Aug 2022 23:06:50 +0300 Subject: stage2: check for zero in `@intToError` safety --- src/Sema.zig | 22 ++++++++++++---------- src/codegen/llvm.zig | 2 +- src/type.zig | 2 ++ test/cases/safety/zero casted to error.zig | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 11 deletions(-) create mode 100644 test/cases/safety/zero casted to error.zig (limited to 'src/codegen/llvm.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 3e62344a04..55b519d900 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6804,11 +6804,10 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const uncasted_operand = try sema.resolveInst(extra.operand); const operand = try sema.coerce(block, Type.anyerror, uncasted_operand, operand_src); - const result_ty = Type.u16; if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { if (val.isUndef()) { - return sema.addConstUndef(result_ty); + return sema.addConstUndef(Type.err_int); } switch (val.tag()) { .@"error" => { @@ -6817,14 +6816,14 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat .base = .{ .tag = .int_u64 }, .data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value, }; - return sema.addConstant(result_ty, Value.initPayload(&payload.base)); + return sema.addConstant(Type.err_int, Value.initPayload(&payload.base)); }, // This is not a valid combination with the type `anyerror`. .the_only_possible_value => unreachable, // Assume it's already encoded as an integer. - else => return sema.addConstant(result_ty, val), + else => return sema.addConstant(Type.err_int, val), } } @@ -6833,14 +6832,14 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat if (!op_ty.isAnyError()) { const names = op_ty.errorSetNames(); switch (names.len) { - 0 => return sema.addConstant(result_ty, Value.zero), - 1 => return sema.addIntUnsigned(result_ty, sema.mod.global_error_set.get(names[0]).?), + 0 => return sema.addConstant(Type.err_int, Value.zero), + 1 => return sema.addIntUnsigned(Type.err_int, sema.mod.global_error_set.get(names[0]).?), else => {}, } } try sema.requireRuntimeBlock(block, src, operand_src); - return block.addBitCast(result_ty, operand); + return block.addBitCast(Type.err_int, operand); } fn zirIntToError(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { @@ -6851,7 +6850,7 @@ fn zirIntToError(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat const src = LazySrcLoc.nodeOffset(extra.node); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const uncasted_operand = try sema.resolveInst(extra.operand); - const operand = try sema.coerce(block, Type.u16, uncasted_operand, operand_src); + const operand = try sema.coerce(block, Type.err_int, uncasted_operand, operand_src); const target = sema.mod.getTarget(); if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| { @@ -6868,7 +6867,10 @@ fn zirIntToError(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat try sema.requireRuntimeBlock(block, src, operand_src); if (block.wantSafety()) { const is_lt_len = try block.addUnOp(.cmp_lt_errors_len, operand); - try sema.addSafetyCheck(block, is_lt_len, .invalid_error_code); + const zero_val = try sema.addConstant(Type.err_int, Value.zero); + const is_non_zero = try block.addBinOp(.cmp_neq, operand, zero_val); + const ok = try block.addBinOp(.bit_and, is_lt_len, is_non_zero); + try sema.addSafetyCheck(block, ok, .invalid_error_code); } return block.addInst(.{ .tag = .bitcast, @@ -17360,7 +17362,7 @@ fn zirErrSetCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat try sema.requireRuntimeBlock(block, src, operand_src); if (block.wantSafety() and !dest_ty.isAnyError() and sema.mod.comp.bin_file.options.use_llvm) { - const err_int_inst = try block.addBitCast(Type.u16, operand); + const err_int_inst = try block.addBitCast(Type.err_int, operand); const ok = try block.addTyOp(.error_set_has_value, dest_ty, err_int_inst); try sema.addSafetyCheck(block, ok, .invalid_error_code); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5da3e7e327..0586c99432 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -8005,7 +8005,7 @@ pub const FuncGen = struct { .data = err_int, }; break :int try self.dg.lowerValue(.{ - .ty = Type.u16, + .ty = Type.err_int, .val = Value.initPayload(&tag_val_payload.base), }); }; diff --git a/src/type.zig b/src/type.zig index 1b71f4e9b1..582ea230ef 100644 --- a/src/type.zig +++ b/src/type.zig @@ -6303,6 +6303,8 @@ pub const Type = extern union { pub const @"anyopaque" = initTag(.anyopaque); pub const @"null" = initTag(.@"null"); + pub const err_int = Type.u16; + pub fn ptr(arena: Allocator, mod: *Module, data: Payload.Pointer.Data) !Type { const target = mod.getTarget(); diff --git a/test/cases/safety/zero casted to error.zig b/test/cases/safety/zero casted to error.zig new file mode 100644 index 0000000000..3a2edf834a --- /dev/null +++ b/test/cases/safety/zero casted to error.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace) noreturn { + _ = stack_trace; + if (std.mem.eql(u8, message, "invalid error code")) { + std.process.exit(0); + } + std.process.exit(1); +} +pub fn main() !void { + bar(0) catch {}; + return error.TestFailed; +} +fn bar(x: u16) anyerror { + return @intToError(x); +} +// run +// backend=llvm +// target=native -- cgit v1.2.3 From 6e313eb1107d4f5d7b0ada0a67c810ce90e79bf5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jul 2022 16:27:37 -0700 Subject: stage2: agree with LLVM that `@alignOf(u128)` is 8 on x86_64 and similar targets. --- lib/std/target.zig | 7 ++++--- src/Module.zig | 30 +++++++++++++++++++++++++----- src/Sema.zig | 5 +---- src/codegen/llvm.zig | 14 ++++++++------ src/type.zig | 13 +++++++++++-- test/behavior/align.zig | 29 +++++++++++++++++++++-------- 6 files changed, 70 insertions(+), 28 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/lib/std/target.zig b/lib/std/target.zig index f77f73bce0..155c59bdfc 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1806,9 +1806,9 @@ pub const Target = struct { else => 4, }, - // For x86_64, LLVMABIAlignmentOfType(i128) reports 8. However I think 16 - // is a better number for two reasons: - // 1. Better machine code when loading into SIMD register. + // For these, LLVMABIAlignmentOfType(i128) reports 8. Note that 16 + // is a relevant number in three cases: + // 1. Different machine code instruction when loading into SIMD register. // 2. The C ABI wants 16 for extern structs. // 3. 16-byte cmpxchg needs 16-byte alignment. // Same logic for riscv64, powerpc64, mips64, sparc64. @@ -1819,6 +1819,7 @@ pub const Target = struct { .mips64, .mips64el, .sparc64, + => 8, // Even LLVMABIAlignmentOfType(i128) agrees on these targets. .aarch64, diff --git a/src/Module.zig b/src/Module.zig index 995fdda7ea..6d2180e8e7 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -935,13 +935,33 @@ pub const Struct = struct { /// If true then `default_val` is the comptime field value. is_comptime: bool, - /// Returns the field alignment, assuming the struct is not packed. - pub fn normalAlignment(field: Field, target: Target) u32 { - if (field.abi_align == 0) { - return field.ty.abiAlignment(target); - } else { + /// Returns the field alignment. If the struct is packed, returns 0. + pub fn alignment( + field: Field, + target: Target, + layout: std.builtin.Type.ContainerLayout, + ) u32 { + if (field.abi_align != 0) { + assert(layout != .Packed); return field.abi_align; } + + switch (layout) { + .Packed => return 0, + .Auto => return field.ty.abiAlignment(target), + .Extern => { + // This logic is duplicated in Type.abiAlignmentAdvanced. + const ty_abi_align = field.ty.abiAlignment(target); + + if (field.ty.isAbiInt() and field.ty.intInfo(target).bits >= 128) { + // The C ABI requires 128 bit integer fields of structs + // to be 16-bytes aligned. + return @maximum(ty_abi_align, 16); + } + + return ty_abi_align; + }, + } } }; diff --git a/src/Sema.zig b/src/Sema.zig index d6ac3d0276..32b95a4c21 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -14380,10 +14380,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai else field.default_val; const default_val_ptr = try sema.optRefValue(block, src, field.ty, opt_default_val); - const alignment = switch (layout) { - .Auto, .Extern => field.normalAlignment(target), - .Packed => 0, - }; + const alignment = field.alignment(target, layout); struct_field_fields.* = .{ // name: []const u8, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0586c99432..a9a343439d 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1841,6 +1841,7 @@ pub const Object = struct { } const fields = ty.structFields(); + const layout = ty.containerLayout(); var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{}; defer di_fields.deinit(gpa); @@ -1854,7 +1855,7 @@ pub const Object = struct { if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; const field_size = field.ty.abiSize(target); - const field_align = field.normalAlignment(target); + const field_align = field.alignment(target, layout); const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align); offset = field_offset + field_size; @@ -2499,7 +2500,7 @@ pub const DeclGen = struct { fn lowerType(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type { const llvm_ty = try lowerTypeInner(dg, t); - if (std.debug.runtime_safety and false) check: { + if (std.debug.runtime_safety) check: { if (t.zigTypeTag() == .Opaque) break :check; if (!t.hasRuntimeBits()) break :check; if (!llvm_ty.isSized().toBool()) break :check; @@ -2757,7 +2758,7 @@ pub const DeclGen = struct { for (struct_obj.fields.values()) |field| { if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; - const field_align = field.normalAlignment(target); + const field_align = field.alignment(target, struct_obj.layout); const field_ty_align = field.ty.abiAlignment(target); any_underaligned_fields = any_underaligned_fields or field_align < field_ty_align; @@ -3433,7 +3434,7 @@ pub const DeclGen = struct { for (struct_obj.fields.values()) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; - const field_align = field.normalAlignment(target); + const field_align = field.alignment(target, struct_obj.layout); big_align = @maximum(big_align, field_align); const prev_offset = offset; offset = std.mem.alignForwardGeneric(u64, offset, field_align); @@ -9376,13 +9377,14 @@ fn llvmFieldIndex( } return null; } - assert(ty.containerLayout() != .Packed); + const layout = ty.containerLayout(); + assert(layout != .Packed); var llvm_field_index: c_uint = 0; for (ty.structFields().values()) |field, i| { if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; - const field_align = field.normalAlignment(target); + const field_align = field.alignment(target, layout); big_align = @maximum(big_align, field_align); const prev_offset = offset; offset = std.mem.alignForwardGeneric(u64, offset, field_align); diff --git a/src/type.zig b/src/type.zig index 700f524556..4432ae9cf3 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3017,6 +3017,15 @@ pub const Type = extern union { }, }; big_align = @maximum(big_align, field_align); + + // This logic is duplicated in Module.Struct.Field.alignment. + if (struct_obj.layout == .Extern) { + if (field.ty.isAbiInt() and field.ty.intInfo(target).bits >= 128) { + // The C ABI requires 128 bit integer fields of structs + // to be 16-bytes aligned. + big_align = @maximum(big_align, 16); + } + } } return AbiAlignmentAdvanced{ .scalar = big_align }; }, @@ -5490,7 +5499,7 @@ pub const Type = extern union { .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; assert(struct_obj.layout != .Packed); - return struct_obj.fields.values()[index].normalAlignment(target); + return struct_obj.fields.values()[index].alignment(target, struct_obj.layout); }, .@"union", .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; @@ -5597,7 +5606,7 @@ pub const Type = extern union { if (!field.ty.hasRuntimeBits() or field.is_comptime) return FieldOffset{ .field = it.field, .offset = it.offset }; - const field_align = field.normalAlignment(it.target); + const field_align = field.alignment(it.target, it.struct_obj.layout); it.big_align = @maximum(it.big_align, field_align); it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align); defer it.offset += field.ty.abiSize(it.target); diff --git a/test/behavior/align.zig b/test/behavior/align.zig index 26e3d91373..4a824bc9cf 100644 --- a/test/behavior/align.zig +++ b/test/behavior/align.zig @@ -143,6 +143,19 @@ test "alignment and size of structs with 128-bit fields" { .riscv64, .sparc64, .x86_64, + => .{ + .a_align = 8, + .a_size = 16, + + .b_align = 16, + .b_size = 32, + + .u128_align = 8, + .u128_size = 16, + .u129_align = 8, + .u129_size = 24, + }, + .aarch64, .aarch64_be, .aarch64_32, @@ -166,17 +179,17 @@ test "alignment and size of structs with 128-bit fields" { else => return error.SkipZigTest, }; comptime { - std.debug.assert(@alignOf(A) == expected.a_align); - std.debug.assert(@sizeOf(A) == expected.a_size); + assert(@alignOf(A) == expected.a_align); + assert(@sizeOf(A) == expected.a_size); - std.debug.assert(@alignOf(B) == expected.b_align); - std.debug.assert(@sizeOf(B) == expected.b_size); + assert(@alignOf(B) == expected.b_align); + assert(@sizeOf(B) == expected.b_size); - std.debug.assert(@alignOf(u128) == expected.u128_align); - std.debug.assert(@sizeOf(u128) == expected.u128_size); + assert(@alignOf(u128) == expected.u128_align); + assert(@sizeOf(u128) == expected.u128_size); - std.debug.assert(@alignOf(u129) == expected.u129_align); - std.debug.assert(@sizeOf(u129) == expected.u129_size); + assert(@alignOf(u129) == expected.u129_align); + assert(@sizeOf(u129) == expected.u129_size); } } -- cgit v1.2.3 From 517eb73b23ee77672abe6b20b79b6648cd3ff879 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 16 Jul 2022 17:03:05 -0700 Subject: LLVM: disable the ABI size safety check There still more instances of this check being tripped during behavior tests that we need to fix before we can enable this. --- src/codegen/llvm.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a9a343439d..983813d780 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -2500,7 +2500,7 @@ pub const DeclGen = struct { fn lowerType(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type { const llvm_ty = try lowerTypeInner(dg, t); - if (std.debug.runtime_safety) check: { + if (std.debug.runtime_safety and false) check: { if (t.zigTypeTag() == .Opaque) break :check; if (!t.hasRuntimeBits()) break :check; if (!llvm_ty.isSized().toBool()) break :check; -- cgit v1.2.3 From b975f7a56fec9e0e7aca9832282bc772c743d731 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Aug 2022 18:58:28 -0700 Subject: std.Target gains ObjectFormat field --- lib/std/target.zig | 33 ++++++++++----------- lib/std/zig.zig | 12 ++++---- lib/std/zig/CrossTarget.zig | 13 ++++++++- lib/std/zig/system/NativeTargetInfo.zig | 5 ++++ src/Compilation.zig | 30 +++++++++---------- src/Sema.zig | 2 +- src/codegen/llvm.zig | 2 +- src/link.zig | 11 ++++--- src/link/C.zig | 2 +- src/link/Coff.zig | 2 +- src/link/Elf.zig | 2 +- src/link/MachO.zig | 4 +-- src/link/NvPtx.zig | 2 +- src/link/Plan9.zig | 2 +- src/link/SpirV.zig | 2 +- src/link/Wasm.zig | 2 +- src/main.zig | 51 ++++++++++++++------------------- src/stage1/codegen.cpp | 1 + 18 files changed, 93 insertions(+), 85 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/lib/std/target.zig b/lib/std/target.zig index 155c59bdfc..00553bb520 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -9,6 +9,7 @@ pub const Target = struct { cpu: Cpu, os: Os, abi: Abi, + ofmt: ObjectFormat, pub const Os = struct { tag: Tag, @@ -594,6 +595,20 @@ pub const Target = struct { .nvptx => ".ptx", }; } + + pub fn default(os_tag: Os.Tag, cpu_arch: Cpu.Arch) ObjectFormat { + return switch (os_tag) { + .windows, .uefi => .coff, + .ios, .macos, .watchos, .tvos => .macho, + .plan9 => .plan9, + else => return switch (cpu_arch) { + .wasm32, .wasm64 => .wasm, + .spirv32, .spirv64 => .spirv, + .nvptx, .nvptx64 => .nvptx, + else => .elf, + }, + }; + } }; pub const SubSystem = enum { @@ -1381,24 +1396,6 @@ pub const Target = struct { return libPrefix_os_abi(self.os.tag, self.abi); } - pub fn getObjectFormatSimple(os_tag: Os.Tag, cpu_arch: Cpu.Arch) ObjectFormat { - return switch (os_tag) { - .windows, .uefi => .coff, - .ios, .macos, .watchos, .tvos => .macho, - .plan9 => .plan9, - else => return switch (cpu_arch) { - .wasm32, .wasm64 => .wasm, - .spirv32, .spirv64 => .spirv, - .nvptx, .nvptx64 => .nvptx, - else => .elf, - }, - }; - } - - pub fn getObjectFormat(self: Target) ObjectFormat { - return getObjectFormatSimple(self.os.tag, self.cpu.arch); - } - pub fn isMinGW(self: Target) bool { return self.os.tag == .windows and self.isGnu(); } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index b8f75f649e..85835ebf18 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -103,7 +103,6 @@ pub const BinNameOptions = struct { target: std.Target, output_mode: std.builtin.OutputMode, link_mode: ?std.builtin.LinkMode = null, - object_format: ?std.Target.ObjectFormat = null, version: ?std.builtin.Version = null, }; @@ -111,8 +110,7 @@ pub const BinNameOptions = struct { pub fn binNameAlloc(allocator: std.mem.Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 { const root_name = options.root_name; const target = options.target; - const ofmt = options.object_format orelse target.getObjectFormat(); - switch (ofmt) { + switch (target.ofmt) { .coff => switch (options.output_mode) { .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }), .Lib => { @@ -186,8 +184,12 @@ pub fn binNameAlloc(allocator: std.mem.Allocator, options: BinNameOptions) error .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), .plan9 => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), - .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, ofmt.fileExt(target.cpu.arch) }), - .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ target.libPrefix(), root_name }), + .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ + root_name, target.ofmt.fileExt(target.cpu.arch), + }), + .Lib => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{ + target.libPrefix(), root_name, + }), }, .nvptx => return std.fmt.allocPrint(allocator, "{s}", .{root_name}), } diff --git a/lib/std/zig/CrossTarget.zig b/lib/std/zig/CrossTarget.zig index 196afbc835..a4e9b2576d 100644 --- a/lib/std/zig/CrossTarget.zig +++ b/lib/std/zig/CrossTarget.zig @@ -42,6 +42,9 @@ abi: ?Target.Abi = null, /// based on the `os_tag`. dynamic_linker: DynamicLinker = DynamicLinker{}, +/// `null` means default for the cpu/arch/os combo. +ofmt: ?Target.ObjectFormat = null, + pub const CpuModel = union(enum) { /// Always native native, @@ -168,6 +171,7 @@ pub fn toTarget(self: CrossTarget) Target { .cpu = self.getCpu(), .os = self.getOs(), .abi = self.getAbi(), + .ofmt = self.getObjectFormat(), }; } @@ -197,6 +201,8 @@ pub const ParseOptions = struct { /// detected path, or a standard path. dynamic_linker: ?[]const u8 = null, + object_format: ?[]const u8 = null, + /// If this is provided, the function will populate some information about parsing failures, /// so that user-friendly error messages can be delivered. diagnostics: ?*Diagnostics = null, @@ -321,6 +327,11 @@ pub fn parse(args: ParseOptions) !CrossTarget { } } + if (args.object_format) |ofmt_name| { + result.ofmt = std.meta.stringToEnum(Target.ObjectFormat, ofmt_name) orelse + return error.UnknownObjectFormat; + } + return result; } @@ -620,7 +631,7 @@ pub fn setGnuLibCVersion(self: *CrossTarget, major: u32, minor: u32, patch: u32) } pub fn getObjectFormat(self: CrossTarget) Target.ObjectFormat { - return Target.getObjectFormatSimple(self.getOsTag(), self.getCpuArch()); + return self.ofmt orelse Target.ObjectFormat.default(self.getOsTag(), self.getCpuArch()); } pub fn updateCpuFeatures(self: CrossTarget, set: *Target.Cpu.Feature.Set) void { diff --git a/lib/std/zig/system/NativeTargetInfo.zig b/lib/std/zig/system/NativeTargetInfo.zig index 6dcedd93b8..4f8c0025c5 100644 --- a/lib/std/zig/system/NativeTargetInfo.zig +++ b/lib/std/zig/system/NativeTargetInfo.zig @@ -276,6 +276,7 @@ fn detectAbiAndDynamicLinker( }; var ld_info_list_buffer: [all_abis.len]LdInfo = undefined; var ld_info_list_len: usize = 0; + const ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch); for (all_abis) |abi| { // This may be a nonsensical parameter. We detect this with error.UnknownDynamicLinkerPath and @@ -284,6 +285,7 @@ fn detectAbiAndDynamicLinker( .cpu = cpu, .os = os, .abi = abi, + .ofmt = ofmt, }; const ld = target.standardDynamicLinkerPath(); if (ld.get() == null) continue; @@ -346,6 +348,7 @@ fn detectAbiAndDynamicLinker( .cpu = cpu, .os = os_adjusted, .abi = cross_target.abi orelse found_ld_info.abi, + .ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os_adjusted.tag, cpu.arch), }, .dynamic_linker = if (cross_target.dynamic_linker.get() == null) DynamicLinker.init(found_ld_path) @@ -539,6 +542,7 @@ pub fn abiAndDynamicLinkerFromFile( .cpu = cpu, .os = os, .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), + .ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), }, .dynamic_linker = cross_target.dynamic_linker, }; @@ -829,6 +833,7 @@ fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, cross_target: Cros .cpu = cpu, .os = os, .abi = cross_target.abi orelse Target.Abi.default(cpu.arch, os), + .ofmt = cross_target.ofmt orelse Target.ObjectFormat.default(os.tag, cpu.arch), }; return NativeTargetInfo{ .target = target, diff --git a/src/Compilation.zig b/src/Compilation.zig index af39154a3f..5e0d815c96 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -810,7 +810,6 @@ pub const InitOptions = struct { /// this flag would be set to disable this machinery to avoid false positives. disable_lld_caching: bool = false, cache_mode: CacheMode = .incremental, - object_format: ?std.Target.ObjectFormat = null, optimize_mode: std.builtin.Mode = .Debug, keep_source_files_loaded: bool = false, clang_argv: []const []const u8 = &[0][]const u8{}, @@ -1027,8 +1026,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const comp = try arena.create(Compilation); const root_name = try arena.dupeZ(u8, options.root_name); - const ofmt = options.object_format orelse options.target.getObjectFormat(); - const use_stage1 = options.use_stage1 orelse blk: { // Even though we may have no Zig code to compile (depending on `options.main_pkg`), // we may need to use stage1 for building compiler-rt and other dependencies. @@ -1042,7 +1039,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { } // If LLVM does not support the target, then we can't use it. - if (!target_util.hasLlvmSupport(options.target, ofmt)) + if (!target_util.hasLlvmSupport(options.target, options.target.ofmt)) break :blk false; break :blk build_options.is_stage1; @@ -1072,7 +1069,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { break :blk true; // If LLVM does not support the target, then we can't use it. - if (!target_util.hasLlvmSupport(options.target, ofmt)) + if (!target_util.hasLlvmSupport(options.target, options.target.ofmt)) break :blk false; // Prefer LLVM for release builds. @@ -1115,7 +1112,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { if (!build_options.have_llvm) break :blk false; - if (ofmt == .c) + if (options.target.ofmt == .c) break :blk false; if (options.want_lto) |lto| { @@ -1374,7 +1371,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { cache.hash.add(options.target.os.getVersionRange()); cache.hash.add(options.is_native_os); cache.hash.add(options.target.abi); - cache.hash.add(ofmt); + cache.hash.add(options.target.ofmt); cache.hash.add(pic); cache.hash.add(pie); cache.hash.add(lto); @@ -1682,7 +1679,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .sysroot = sysroot, .output_mode = options.output_mode, .link_mode = link_mode, - .object_format = ofmt, .optimize_mode = options.optimize_mode, .use_lld = use_lld, .use_llvm = use_llvm, @@ -1841,7 +1837,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const have_bin_emit = comp.bin_file.options.emit != null or comp.whole_bin_sub_path != null; - if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies) { + if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and + options.target.ofmt != .c) + { if (comp.getTarget().isDarwin()) { switch (comp.getTarget().abi) { .none, @@ -3739,7 +3737,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: *std.P else c_source_basename[0 .. c_source_basename.len - std.fs.path.extension(c_source_basename).len]; - const o_ext = comp.bin_file.options.object_format.fileExt(comp.bin_file.options.target.cpu.arch); + const target = comp.getTarget(); + const o_ext = target.ofmt.fileExt(target.cpu.arch); const digest = if (!comp.disable_c_depfile and try man.hit()) man.final() else blk: { var argv = std.ArrayList([]const u8).init(comp.gpa); defer argv.deinit(); @@ -4092,7 +4091,7 @@ pub fn addCCArgs( if (!comp.bin_file.options.strip) { try argv.append("-g"); - switch (comp.bin_file.options.object_format) { + switch (target.ofmt) { .coff => try argv.append("-gcodeview"), else => {}, } @@ -4660,7 +4659,7 @@ fn wantBuildLibCFromSource(comp: Compilation) bool { }; return comp.bin_file.options.link_libc and is_exe_or_dyn_lib and comp.bin_file.options.libc_installation == null and - comp.bin_file.options.object_format != .c; + comp.bin_file.options.target.ofmt != .c; } fn wantBuildGLibCFromSource(comp: Compilation) bool { @@ -4688,7 +4687,7 @@ fn wantBuildLibUnwindFromSource(comp: *Compilation) bool { .Exe => true, }; return is_exe_or_dyn_lib and comp.bin_file.options.link_libunwind and - comp.bin_file.options.object_format != .c; + comp.bin_file.options.target.ofmt != .c; } fn setAllocFailure(comp: *Compilation) void { @@ -4747,7 +4746,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca const zig_backend: std.builtin.CompilerBackend = blk: { if (use_stage1) break :blk .stage1; if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm; - if (comp.bin_file.options.object_format == .c) break :blk .stage2_c; + if (target.ofmt == .c) break :blk .stage2_c; break :blk switch (target.cpu.arch) { .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm, .arm, .armeb, .thumb, .thumbeb => .stage2_arm, @@ -4895,6 +4894,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca \\ .cpu = cpu, \\ .os = os, \\ .abi = abi, + \\ .ofmt = object_format, \\}}; \\pub const object_format = std.Target.ObjectFormat.{}; \\pub const mode = std.builtin.Mode.{}; @@ -4909,7 +4909,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca \\pub const code_model = std.builtin.CodeModel.{}; \\ , .{ - std.zig.fmtId(@tagName(comp.bin_file.options.object_format)), + std.zig.fmtId(@tagName(target.ofmt)), std.zig.fmtId(@tagName(comp.bin_file.options.optimize_mode)), link_libc, comp.bin_file.options.link_libcpp, diff --git a/src/Sema.zig b/src/Sema.zig index 32b95a4c21..d059ee09a3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20654,7 +20654,7 @@ fn panicWithMsg( const arena = sema.arena; const this_feature_is_implemented_in_the_backend = - mod.comp.bin_file.options.object_format == .c or + mod.comp.bin_file.options.target.ofmt == .c or mod.comp.bin_file.options.use_llvm; if (!this_feature_is_implemented_in_the_backend) { // TODO implement this feature in all the backends and then delete this branch diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 983813d780..8c84f61a81 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -273,7 +273,7 @@ pub const Object = struct { var di_compile_unit: ?*llvm.DICompileUnit = null; if (!options.strip) { - switch (options.object_format) { + switch (options.target.ofmt) { .coff => llvm_module.addModuleCodeViewFlag(), else => llvm_module.addModuleDebugInfoFlag(), } diff --git a/src/link.zig b/src/link.zig index a6dafce20e..31b54f705a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -72,7 +72,6 @@ pub const Options = struct { target: std.Target, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, - object_format: std.Target.ObjectFormat, optimize_mode: std.builtin.Mode, machine_code_model: std.builtin.CodeModel, root_name: [:0]const u8, @@ -273,13 +272,13 @@ pub const File = struct { /// rewriting it. A malicious file is detected as incremental link failure /// and does not cause Illegal Behavior. This operation is not atomic. pub fn openPath(allocator: Allocator, options: Options) !*File { - if (options.object_format == .macho) { + if (options.target.ofmt == .macho) { return &(try MachO.openPath(allocator, options)).base; } const use_stage1 = build_options.is_stage1 and options.use_stage1; if (use_stage1 or options.emit == null) { - return switch (options.object_format) { + return switch (options.target.ofmt) { .coff => &(try Coff.createEmpty(allocator, options)).base, .elf => &(try Elf.createEmpty(allocator, options)).base, .macho => unreachable, @@ -298,7 +297,7 @@ pub const File = struct { if (options.module == null) { // No point in opening a file, we would not write anything to it. // Initialize with empty. - return switch (options.object_format) { + return switch (options.target.ofmt) { .coff => &(try Coff.createEmpty(allocator, options)).base, .elf => &(try Elf.createEmpty(allocator, options)).base, .macho => unreachable, @@ -314,12 +313,12 @@ pub const File = struct { // Open a temporary object file, not the final output file because we // want to link with LLD. break :blk try std.fmt.allocPrint(allocator, "{s}{s}", .{ - emit.sub_path, options.object_format.fileExt(options.target.cpu.arch), + emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), }); } else emit.sub_path; errdefer if (use_lld) allocator.free(sub_path); - const file: *File = switch (options.object_format) { + const file: *File = switch (options.target.ofmt) { .coff => &(try Coff.openPath(allocator, sub_path, options)).base, .elf => &(try Elf.openPath(allocator, sub_path, options)).base, .macho => unreachable, diff --git a/src/link/C.zig b/src/link/C.zig index 6449be9c56..955044f90d 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -48,7 +48,7 @@ const DeclBlock = struct { }; pub fn openPath(gpa: Allocator, sub_path: []const u8, options: link.Options) !*C { - assert(options.object_format == .c); + assert(options.target.ofmt == .c); if (options.use_llvm) return error.LLVMHasNoCBackend; if (options.use_lld) return error.LLDHasNoCBackend; diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 1b5ddbbf8b..92e9dce3ad 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -128,7 +128,7 @@ pub const TextBlock = struct { pub const SrcFn = void; pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Coff { - assert(options.object_format == .coff); + assert(options.target.ofmt == .coff); if (build_options.have_llvm and options.use_llvm) { return createEmpty(allocator, options); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 917e4c18d1..2f67c35205 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -249,7 +249,7 @@ pub const Export = struct { }; pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Elf { - assert(options.object_format == .elf); + assert(options.target.ofmt == .elf); if (build_options.have_llvm and options.use_llvm) { return createEmpty(allocator, options); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index e4370c703e..7a50843023 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -270,7 +270,7 @@ pub const Export = struct { }; pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { - assert(options.object_format == .macho); + assert(options.target.ofmt == .macho); const use_stage1 = build_options.is_stage1 and options.use_stage1; if (use_stage1 or options.emit == null) { @@ -289,7 +289,7 @@ pub fn openPath(allocator: Allocator, options: link.Options) !*MachO { // we also want to put the intermediary object file in the cache while the // main emit directory is the cwd. self.base.intermediary_basename = try std.fmt.allocPrint(allocator, "{s}{s}", .{ - emit.sub_path, options.object_format.fileExt(options.target.cpu.arch), + emit.sub_path, options.target.ofmt.fileExt(options.target.cpu.arch), }); } diff --git a/src/link/NvPtx.zig b/src/link/NvPtx.zig index bd86d87201..7bf51c7ad3 100644 --- a/src/link/NvPtx.zig +++ b/src/link/NvPtx.zig @@ -57,7 +57,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*NvPtx { pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*NvPtx { if (!build_options.have_llvm) @panic("nvptx target requires a zig compiler with llvm enabled."); if (!options.use_llvm) return error.PtxArchNotSupported; - assert(options.object_format == .nvptx); + assert(options.target.ofmt == .nvptx); const nvptx = try createEmpty(allocator, options); log.info("Opening .ptx target file {s}", .{sub_path}); diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 16f7841c2d..9a8927dcb4 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -657,7 +657,7 @@ pub const base_tag = .plan9; pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Plan9 { if (options.use_llvm) return error.LLVMBackendDoesNotSupportPlan9; - assert(options.object_format == .plan9); + assert(options.target.ofmt == .plan9); const self = try createEmpty(allocator, options); errdefer self.base.destroy(); diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index e295dceb55..b2f6edddfb 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -99,7 +99,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*SpirV { } pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*SpirV { - assert(options.object_format == .spirv); + assert(options.target.ofmt == .spirv); if (options.use_llvm) return error.LLVM_BackendIsTODO_ForSpirV; // TODO: LLVM Doesn't support SpirV at all. if (options.use_lld) return error.LLD_LinkingIsTODO_ForSpirV; // TODO: LLD Doesn't support SpirV at all. diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 1bdd9426a5..6267865888 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -282,7 +282,7 @@ pub const StringTable = struct { }; pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Wasm { - assert(options.object_format == .wasm); + assert(options.target.ofmt == .wasm); if (build_options.have_llvm and options.use_llvm) { return createEmpty(allocator, options); diff --git a/src/main.zig b/src/main.zig index 971fe19e36..dfce4d83b8 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2192,6 +2192,7 @@ fn buildOutputType( .arch_os_abi = target_arch_os_abi, .cpu_features = target_mcpu, .dynamic_linker = target_dynamic_linker, + .object_format = target_ofmt, }; // Before passing the mcpu string in for parsing, we convert any -m flags that were @@ -2494,28 +2495,7 @@ fn buildOutputType( } } - const object_format: std.Target.ObjectFormat = blk: { - const ofmt = target_ofmt orelse break :blk target_info.target.getObjectFormat(); - if (mem.eql(u8, ofmt, "elf")) { - break :blk .elf; - } else if (mem.eql(u8, ofmt, "c")) { - break :blk .c; - } else if (mem.eql(u8, ofmt, "coff")) { - break :blk .coff; - } else if (mem.eql(u8, ofmt, "macho")) { - break :blk .macho; - } else if (mem.eql(u8, ofmt, "wasm")) { - break :blk .wasm; - } else if (mem.eql(u8, ofmt, "hex")) { - break :blk .hex; - } else if (mem.eql(u8, ofmt, "raw")) { - break :blk .raw; - } else if (mem.eql(u8, ofmt, "spirv")) { - break :blk .spirv; - } else { - fatal("unsupported object format: {s}", .{ofmt}); - } - }; + const object_format = target_info.target.ofmt; if (output_mode == .Obj and (object_format == .coff or object_format == .macho)) { const total_obj_count = c_source_files.items.len + @@ -2569,7 +2549,6 @@ fn buildOutputType( .target = target_info.target, .output_mode = output_mode, .link_mode = link_mode, - .object_format = object_format, .version = optional_version, }), }, @@ -2859,7 +2838,6 @@ fn buildOutputType( .emit_implib = emit_implib_resolved.data, .link_mode = link_mode, .dll_export_fns = dll_export_fns, - .object_format = object_format, .optimize_mode = optimize_mode, .keep_source_files_loaded = false, .clang_argv = clang_argv.items, @@ -3173,11 +3151,11 @@ fn parseCrossTargetOrReportFatalError( for (diags.arch.?.allCpuModels()) |cpu| { help_text.writer().print(" {s}\n", .{cpu.name}) catch break :help; } - std.log.info("Available CPUs for architecture '{s}':\n{s}", .{ + std.log.info("available CPUs for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, }); } - fatal("Unknown CPU: '{s}'", .{diags.cpu_name.?}); + fatal("unknown CPU: '{s}'", .{diags.cpu_name.?}); }, error.UnknownCpuFeature => { help: { @@ -3186,11 +3164,26 @@ fn parseCrossTargetOrReportFatalError( for (diags.arch.?.allFeaturesList()) |feature| { help_text.writer().print(" {s}: {s}\n", .{ feature.name, feature.description }) catch break :help; } - std.log.info("Available CPU features for architecture '{s}':\n{s}", .{ + std.log.info("available CPU features for architecture '{s}':\n{s}", .{ @tagName(diags.arch.?), help_text.items, }); } - fatal("Unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?}); + fatal("unknown CPU feature: '{s}'", .{diags.unknown_feature_name.?}); + }, + error.UnknownObjectFormat => { + { + var help_text = std.ArrayList(u8).init(allocator); + defer help_text.deinit(); + inline for (@typeInfo(std.Target.ObjectFormat).Enum.fields) |field| { + help_text.writer().print(" {s}\n", .{field.name}) catch + // TODO change this back to `break :help` + // this working around a stage1 bug. + //break :help; + @panic("out of memory"); + } + std.log.info("available object formats:\n{s}", .{help_text.items}); + } + fatal("unknown object format: '{s}'", .{opts.object_format.?}); }, else => |e| return e, }; @@ -3360,7 +3353,7 @@ fn updateModule(gpa: Allocator, comp: *Compilation, hook: AfterUpdateHook) !void // If a .pdb file is part of the expected output, we must also copy // it into place here. - const is_coff = comp.bin_file.options.object_format == .coff; + const is_coff = comp.bin_file.options.target.ofmt == .coff; const have_pdb = is_coff and !comp.bin_file.options.strip; if (have_pdb) { // Replace `.out` or `.exe` with `.pdb` on both the source and destination diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 7010e736dc..d4a09f7968 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -10082,6 +10082,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { " .cpu = cpu,\n" " .os = os,\n" " .abi = abi,\n" + " .ofmt = object_format,\n" "};\n" ); -- cgit v1.2.3 From c0b7f20893ea5ca42e0d02b59db6f459c2f80ca1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 17 Aug 2022 18:42:39 -0700 Subject: stage2: implement stack protectors This is one of the final remaining TODOs for the LLVM backend. --- src/Compilation.zig | 121 ++++++++++++++++++++++++++--------------- src/clang_options_data.zig | 45 +++++++++++++-- src/codegen/llvm.zig | 11 +++- src/glibc.zig | 1 + src/libcxx.zig | 2 + src/libtsan.zig | 1 + src/libunwind.zig | 1 + src/link.zig | 3 + src/link/Elf.zig | 6 ++ src/main.zig | 16 ++++++ src/musl.zig | 1 + src/target.zig | 9 +++ tools/update_clang_options.zig | 20 +++++++ 13 files changed, 184 insertions(+), 53 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 5e0d815c96..060afba694 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -173,6 +173,7 @@ astgen_wait_group: WaitGroup = .{}, /// TODO: Remove this when Stage2 becomes the default compiler as it will already have this information. export_symbol_names: std.ArrayListUnmanaged([]const u8) = .{}, +pub const default_stack_protector_buffer_size = 4; pub const SemaError = Module.SemaError; pub const CRTFile = struct { @@ -837,6 +838,10 @@ pub const InitOptions = struct { want_pie: ?bool = null, want_sanitize_c: ?bool = null, want_stack_check: ?bool = null, + /// null means default. + /// 0 means no stack protector. + /// other number means stack protection with that buffer size. + want_stack_protector: ?u32 = null, want_red_zone: ?bool = null, omit_frame_pointer: ?bool = null, want_valgrind: ?bool = null, @@ -1014,6 +1019,15 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { return error.ExportTableAndImportTableConflict; } + // The `have_llvm` condition is here only because native backends cannot yet build compiler-rt. + // Once they are capable this condition could be removed. When removing this condition, + // also test the use case of `build-obj -fcompiler-rt` with the native backends + // and make sure the compiler-rt symbols are emitted. + const capable_of_building_compiler_rt = build_options.have_llvm; + + const capable_of_building_zig_libc = build_options.have_llvm; + const capable_of_building_ssp = build_options.have_llvm; + const comp: *Compilation = comp: { // For allocations that have the same lifetime as Compilation. This arena is used only during this // initialization and then is freed in deinit(). @@ -1289,11 +1303,36 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const sanitize_c = options.want_sanitize_c orelse is_safe_mode; - const stack_check: bool = b: { - if (!target_util.supportsStackProbing(options.target)) - break :b false; - break :b options.want_stack_check orelse is_safe_mode; + const stack_check: bool = options.want_stack_check orelse b: { + if (!target_util.supportsStackProbing(options.target)) break :b false; + break :b is_safe_mode; }; + if (stack_check and !target_util.supportsStackProbing(options.target)) + return error.StackCheckUnsupportedByTarget; + + const stack_protector: u32 = options.want_stack_protector orelse b: { + if (!target_util.supportsStackProtector(options.target)) break :b @as(u32, 0); + + // This logic is checking for linking libc because otherwise our start code + // which is trying to set up TLS (i.e. the fs/gs registers) but the stack + // protection code depends on fs/gs registers being already set up. + // If we were able to annotate start code, or perhaps the entire std lib, + // as being exempt from stack protection checks, we could change this logic + // to supporting stack protection even when not linking libc. + // TODO file issue about this + if (!link_libc) break :b 0; + if (!capable_of_building_ssp) break :b 0; + if (is_safe_mode) break :b default_stack_protector_buffer_size; + break :b 0; + }; + if (stack_protector != 0) { + if (!target_util.supportsStackProtector(options.target)) + return error.StackProtectorUnsupportedByTarget; + if (!capable_of_building_ssp) + return error.StackProtectorUnsupportedByBackend; + if (!link_libc) + return error.StackProtectorUnavailableWithoutLibC; + } const valgrind: bool = b: { if (!target_util.hasValgrindSupport(options.target)) @@ -1378,6 +1417,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { cache.hash.add(unwind_tables); cache.hash.add(tsan); cache.hash.add(stack_check); + cache.hash.add(stack_protector); cache.hash.add(red_zone); cache.hash.add(omit_frame_pointer); cache.hash.add(link_mode); @@ -1741,6 +1781,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .valgrind = valgrind, .tsan = tsan, .stack_check = stack_check, + .stack_protector = stack_protector, .red_zone = red_zone, .omit_frame_pointer = omit_frame_pointer, .single_threaded = single_threaded, @@ -1822,6 +1863,8 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }; errdefer comp.destroy(); + const target = comp.getTarget(); + // Add a `CObject` for each `c_source_files`. try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len); for (options.c_source_files) |c_source_file| { @@ -1837,11 +1880,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { const have_bin_emit = comp.bin_file.options.emit != null or comp.whole_bin_sub_path != null; - if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and - options.target.ofmt != .c) - { - if (comp.getTarget().isDarwin()) { - switch (comp.getTarget().abi) { + if (have_bin_emit and !comp.bin_file.options.skip_linker_dependencies and target.ofmt != .c) { + if (target.isDarwin()) { + switch (target.abi) { .none, .simulator, .macabi, @@ -1852,9 +1893,9 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { // If we need to build glibc for the target, add work items for it. // We go through the work queue so that building can be done in parallel. if (comp.wantBuildGLibCFromSource()) { - if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable; + if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; - if (glibc.needsCrtiCrtn(comp.getTarget())) { + if (glibc.needsCrtiCrtn(target)) { try comp.work_queue.write(&[_]Job{ .{ .glibc_crt_file = .crti_o }, .{ .glibc_crt_file = .crtn_o }, @@ -1867,10 +1908,10 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }); } if (comp.wantBuildMuslFromSource()) { - if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable; + if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; try comp.work_queue.ensureUnusedCapacity(6); - if (musl.needsCrtiCrtn(comp.getTarget())) { + if (musl.needsCrtiCrtn(target)) { comp.work_queue.writeAssumeCapacity(&[_]Job{ .{ .musl_crt_file = .crti_o }, .{ .musl_crt_file = .crtn_o }, @@ -1887,7 +1928,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }); } if (comp.wantBuildWasiLibcFromSource()) { - if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable; + if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; const wasi_emulated_libs = comp.bin_file.options.wasi_emulated_libs; try comp.work_queue.ensureUnusedCapacity(wasi_emulated_libs.len + 2); // worst-case we need all components @@ -1902,7 +1943,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { }); } if (comp.wantBuildMinGWFromSource()) { - if (!target_util.canBuildLibC(comp.getTarget())) return error.LibCUnavailable; + if (!target_util.canBuildLibC(target)) return error.LibCUnavailable; const static_lib_jobs = [_]Job{ .{ .mingw_crt_file = .mingw32_lib }, @@ -1921,7 +1962,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { } } // Generate Windows import libs. - if (comp.getTarget().os.tag == .windows) { + if (target.os.tag == .windows) { const count = comp.bin_file.options.system_libs.count(); try comp.work_queue.ensureUnusedCapacity(count); var i: usize = 0; @@ -1940,15 +1981,6 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { try comp.work_queue.writeItem(.libtsan); } - // The `have_llvm` condition is here only because native backends cannot yet build compiler-rt. - // Once they are capable this condition could be removed. When removing this condition, - // also test the use case of `build-obj -fcompiler-rt` with the native backends - // and make sure the compiler-rt symbols are emitted. - const capable_of_building_compiler_rt = build_options.have_llvm; - - const capable_of_building_zig_libc = build_options.have_llvm; - const capable_of_building_ssp = comp.bin_file.options.use_stage1; - if (comp.bin_file.options.include_compiler_rt and capable_of_building_compiler_rt) { if (is_exe_or_dyn_lib) { log.debug("queuing a job to build compiler_rt_lib", .{}); @@ -1962,8 +1994,11 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { } } if (needs_c_symbols) { - // MinGW provides no libssp, use our own implementation. - if (comp.getTarget().isMinGW() and capable_of_building_ssp) { + // Related: https://github.com/ziglang/zig/issues/7265. + if (comp.bin_file.options.stack_protector != 0 and + (!comp.bin_file.options.link_libc or + !target_util.libcProvidesStackProtector(target))) + { try comp.work_queue.writeItem(.{ .libssp = {} }); } @@ -4123,6 +4158,17 @@ pub fn addCCArgs( try argv.append("-fno-omit-frame-pointer"); } + const ssp_buf_size = comp.bin_file.options.stack_protector; + if (ssp_buf_size != 0) { + try argv.appendSlice(&[_][]const u8{ + "-fstack-protector-strong", + "--param", + try std.fmt.allocPrint(arena, "ssp-buffer-size={d}", .{ssp_buf_size}), + }); + } else { + try argv.append("-fno-stack-protector"); + } + switch (comp.bin_file.options.optimize_mode) { .Debug => { // windows c runtime requires -D_DEBUG if using debug libraries @@ -4131,27 +4177,12 @@ pub fn addCCArgs( // to -O1. Besides potentially impairing debugging, -O1/-Og significantly // increases compile times. try argv.append("-O0"); - - if (comp.bin_file.options.link_libc and target.os.tag != .wasi) { - try argv.append("-fstack-protector-strong"); - try argv.append("--param"); - try argv.append("ssp-buffer-size=4"); - } else { - try argv.append("-fno-stack-protector"); - } }, .ReleaseSafe => { // See the comment in the BuildModeFastRelease case for why we pass -O2 rather // than -O3 here. try argv.append("-O2"); - if (comp.bin_file.options.link_libc and target.os.tag != .wasi) { - try argv.append("-D_FORTIFY_SOURCE=2"); - try argv.append("-fstack-protector-strong"); - try argv.append("--param"); - try argv.append("ssp-buffer-size=4"); - } else { - try argv.append("-fno-stack-protector"); - } + try argv.append("-D_FORTIFY_SOURCE=2"); }, .ReleaseFast => { try argv.append("-DNDEBUG"); @@ -4161,12 +4192,10 @@ pub fn addCCArgs( // Zig code than it is for C code. Also, C programmers are used to their code // running in -O2 and thus the -O3 path has been tested less. try argv.append("-O2"); - try argv.append("-fno-stack-protector"); }, .ReleaseSmall => { try argv.append("-DNDEBUG"); try argv.append("-Os"); - try argv.append("-fno-stack-protector"); }, } @@ -5031,6 +5060,7 @@ fn buildOutputFromZig( .use_stage1 = build_options.is_stage1 and comp.bin_file.options.use_stage1, .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, @@ -5311,6 +5341,7 @@ pub fn build_crt_file( .optimize_mode = comp.compilerRtOptMode(), .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, diff --git a/src/clang_options_data.zig b/src/clang_options_data.zig index 76e687c7d6..90ba377847 100644 --- a/src/clang_options_data.zig +++ b/src/clang_options_data.zig @@ -3290,7 +3290,14 @@ flagpd1("fno-stack-arrays"), .psl = false, }, flagpd1("fno-stack-clash-protection"), -flagpd1("fno-stack-protector"), +.{ + .name = "fno-stack-protector", + .syntax = .flag, + .zig_equivalent = .no_stack_protector, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fno-stack-size-section"), flagpd1("fno-standalone-debug"), flagpd1("fno-strength-reduce"), @@ -3588,9 +3595,30 @@ flagpd1("fstack-arrays"), .psl = false, }, flagpd1("fstack-clash-protection"), -flagpd1("fstack-protector"), -flagpd1("fstack-protector-all"), -flagpd1("fstack-protector-strong"), +.{ + .name = "fstack-protector", + .syntax = .flag, + .zig_equivalent = .stack_protector, + .pd1 = true, + .pd2 = false, + .psl = false, +}, +.{ + .name = "fstack-protector-all", + .syntax = .flag, + .zig_equivalent = .stack_protector, + .pd1 = true, + .pd2 = false, + .psl = false, +}, +.{ + .name = "fstack-protector-strong", + .syntax = .flag, + .zig_equivalent = .stack_protector, + .pd1 = true, + .pd2 = false, + .psl = false, +}, flagpd1("fstack-size-section"), flagpd1("fstack-usage"), flagpd1("fstandalone-debug"), @@ -4809,7 +4837,14 @@ flagpd1("single_module"), }, sepd1("split-dwarf-file"), sepd1("split-dwarf-output"), -sepd1("stack-protector"), +.{ + .name = "stack-protector", + .syntax = .separate, + .zig_equivalent = .stack_protector, + .pd1 = true, + .pd2 = false, + .psl = false, +}, sepd1("stack-protector-buffer-size"), sepd1("stack-usage-file"), .{ diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8c84f61a81..0898c8fe87 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -711,9 +711,14 @@ pub const Object = struct { DeclGen.removeFnAttr(llvm_func, "noinline"); } - // TODO: port these over from stage1 - // addLLVMFnAttr(llvm_fn, "sspstrong"); - // addLLVMFnAttrStr(llvm_fn, "stack-protector-buffer-size", "4"); + // TODO: disable this if safety is off for the function scope + const ssp_buf_size = module.comp.bin_file.options.stack_protector; + if (ssp_buf_size != 0) { + var buf: [12]u8 = undefined; + const arg = std.fmt.bufPrintZ(&buf, "{d}", .{ssp_buf_size}) catch unreachable; + dg.addFnAttr(llvm_func, "sspstrong"); + dg.addFnAttrString(llvm_func, "stack-protector-buffer-size", arg); + } // TODO: disable this if safety is off for the function scope if (module.comp.bin_file.options.stack_check) { diff --git a/src/glibc.zig b/src/glibc.zig index 4deac5275f..ceb60ff096 100644 --- a/src/glibc.zig +++ b/src/glibc.zig @@ -1111,6 +1111,7 @@ fn buildSharedLib( .optimize_mode = comp.compilerRtOptMode(), .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, diff --git a/src/libcxx.zig b/src/libcxx.zig index 17d88fa03d..b19834f8c3 100644 --- a/src/libcxx.zig +++ b/src/libcxx.zig @@ -206,6 +206,7 @@ pub fn buildLibCXX(comp: *Compilation) !void { .link_mode = link_mode, .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, @@ -349,6 +350,7 @@ pub fn buildLibCXXABI(comp: *Compilation) !void { .link_mode = link_mode, .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, diff --git a/src/libtsan.zig b/src/libtsan.zig index cbbcd11a8a..16e40c16f8 100644 --- a/src/libtsan.zig +++ b/src/libtsan.zig @@ -211,6 +211,7 @@ pub fn buildTsan(comp: *Compilation) !void { .link_mode = link_mode, .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_valgrind = false, .want_tsan = false, .want_pic = true, diff --git a/src/libunwind.zig b/src/libunwind.zig index 0d030be329..fb4f93af02 100644 --- a/src/libunwind.zig +++ b/src/libunwind.zig @@ -113,6 +113,7 @@ pub fn buildStaticLib(comp: *Compilation) !void { .link_mode = link_mode, .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, diff --git a/src/link.zig b/src/link.zig index 31b54f705a..a0c0c5c369 100644 --- a/src/link.zig +++ b/src/link.zig @@ -90,6 +90,9 @@ pub const Options = struct { entry: ?[]const u8, stack_size_override: ?u64, image_base_override: ?u64, + /// 0 means no stack protector + /// other value means stack protector with that buffer size. + stack_protector: u32, cache_mode: CacheMode, include_compiler_rt: bool, /// Set to `true` to omit debug info. diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 2f67c35205..9902886bac 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1673,6 +1673,12 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v } } + // stack-protector. + // Related: https://github.com/ziglang/zig/issues/7265 + if (comp.libssp_static_lib) |ssp| { + try argv.append(ssp.full_object_path); + } + // compiler-rt if (compiler_rt_path) |p| { try argv.append(p); diff --git a/src/main.zig b/src/main.zig index dfce4d83b8..91d171d237 100644 --- a/src/main.zig +++ b/src/main.zig @@ -378,6 +378,8 @@ const usage_build_generic = \\ -fno-lto Force-disable Link Time Optimization \\ -fstack-check Enable stack probing in unsafe builds \\ -fno-stack-check Disable stack probing in safe builds + \\ -fstack-protector Enable stack protection in unsafe builds + \\ -fno-stack-protector Disable stack protection in safe builds \\ -fsanitize-c Enable C undefined behavior detection in unsafe builds \\ -fno-sanitize-c Disable C undefined behavior detection in safe builds \\ -fvalgrind Include valgrind client requests in release builds @@ -668,6 +670,7 @@ fn buildOutputType( var want_unwind_tables: ?bool = null; var want_sanitize_c: ?bool = null; var want_stack_check: ?bool = null; + var want_stack_protector: ?u32 = null; var want_red_zone: ?bool = null; var omit_frame_pointer: ?bool = null; var want_valgrind: ?bool = null; @@ -1168,6 +1171,10 @@ fn buildOutputType( want_stack_check = true; } else if (mem.eql(u8, arg, "-fno-stack-check")) { want_stack_check = false; + } else if (mem.eql(u8, arg, "-fstack-protector")) { + want_stack_protector = Compilation.default_stack_protector_buffer_size; + } else if (mem.eql(u8, arg, "-fno-stack-protector")) { + want_stack_protector = 0; } else if (mem.eql(u8, arg, "-mred-zone")) { want_red_zone = true; } else if (mem.eql(u8, arg, "-mno-red-zone")) { @@ -1521,6 +1528,12 @@ fn buildOutputType( .no_color_diagnostics => color = .off, .stack_check => want_stack_check = true, .no_stack_check => want_stack_check = false, + .stack_protector => { + if (want_stack_protector == null) { + want_stack_protector = Compilation.default_stack_protector_buffer_size; + } + }, + .no_stack_protector => want_stack_protector = 0, .unwind_tables => want_unwind_tables = true, .no_unwind_tables => want_unwind_tables = false, .nostdlib => ensure_libc_on_non_freestanding = false, @@ -2859,6 +2872,7 @@ fn buildOutputType( .want_unwind_tables = want_unwind_tables, .want_sanitize_c = want_sanitize_c, .want_stack_check = want_stack_check, + .want_stack_protector = want_stack_protector, .want_red_zone = want_red_zone, .omit_frame_pointer = omit_frame_pointer, .want_valgrind = want_valgrind, @@ -4663,6 +4677,8 @@ pub const ClangArgIterator = struct { no_color_diagnostics, stack_check, no_stack_check, + stack_protector, + no_stack_protector, strip, exec_model, emit_llvm, diff --git a/src/musl.zig b/src/musl.zig index 68b524b415..12ff530f8e 100644 --- a/src/musl.zig +++ b/src/musl.zig @@ -215,6 +215,7 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile) !void { .optimize_mode = comp.compilerRtOptMode(), .want_sanitize_c = false, .want_stack_check = false, + .want_stack_protector = 0, .want_red_zone = comp.bin_file.options.red_zone, .omit_frame_pointer = comp.bin_file.options.omit_frame_pointer, .want_valgrind = false, diff --git a/src/target.zig b/src/target.zig index 5202fb15fc..3c8eabe01d 100644 --- a/src/target.zig +++ b/src/target.zig @@ -300,6 +300,15 @@ pub fn supportsStackProbing(target: std.Target) bool { (target.cpu.arch == .i386 or target.cpu.arch == .x86_64); } +pub fn supportsStackProtector(target: std.Target) bool { + _ = target; + return true; +} + +pub fn libcProvidesStackProtector(target: std.Target) bool { + return !target.isMinGW() and target.os.tag != .wasi; +} + pub fn supportsReturnAddress(target: std.Target) bool { return switch (target.cpu.arch) { .wasm32, .wasm64 => target.os.tag == .emscripten, diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index d261b5ee33..f1656546f0 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -352,6 +352,26 @@ const known_options = [_]KnownOpt{ .name = "fno-stack-check", .ident = "no_stack_check", }, + .{ + .name = "stack-protector", + .ident = "stack_protector", + }, + .{ + .name = "fstack-protector", + .ident = "stack_protector", + }, + .{ + .name = "fno-stack-protector", + .ident = "no_stack_protector", + }, + .{ + .name = "fstack-protector-strong", + .ident = "stack_protector", + }, + .{ + .name = "fstack-protector-all", + .ident = "stack_protector", + }, .{ .name = "MD", .ident = "dep_file", -- cgit v1.2.3 From 437311756d88984d9bf3057d9cd93986c15434fa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 18 Aug 2022 10:49:09 -0700 Subject: LLVM: add DLL export attribute This was present in stage1 but missing from self-hosted. --- src/Compilation.zig | 2 +- src/Module.zig | 4 ++++ src/codegen/llvm.zig | 3 +++ src/codegen/llvm/bindings.zig | 9 +++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Compilation.zig b/src/Compilation.zig index 84dd273947..0914685c77 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1231,7 +1231,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { break :blk lm; } else default_link_mode; - const dll_export_fns = if (options.dll_export_fns) |explicit| explicit else is_dyn_lib or options.rdynamic; + const dll_export_fns = options.dll_export_fns orelse (is_dyn_lib or options.rdynamic); const libc_dirs = try detectLibCIncludeDirs( arena, diff --git a/src/Module.zig b/src/Module.zig index 3ae61c264f..3577115ded 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -6529,3 +6529,7 @@ pub fn addGlobalAssembly(mod: *Module, decl_index: Decl.Index, source: []const u mod.global_assembly.putAssumeCapacityNoClobber(decl_index, duped_source); } + +pub fn wantDllExports(mod: Module) bool { + return mod.comp.bin_file.options.dll_export_fns and mod.getTarget().os.tag == .windows; +} diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0898c8fe87..d50b463606 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1103,6 +1103,7 @@ pub const Object = struct { } llvm_global.setUnnamedAddr(.False); llvm_global.setLinkage(.External); + if (module.wantDllExports()) llvm_global.setDLLStorageClass(.Default); if (self.di_map.get(decl)) |di_node| { if (try decl.isFunction()) { const di_func = @ptrCast(*llvm.DISubprogram, di_node); @@ -1128,6 +1129,7 @@ pub const Object = struct { const exp_name = exports[0].options.name; llvm_global.setValueName2(exp_name.ptr, exp_name.len); llvm_global.setUnnamedAddr(.False); + if (module.wantDllExports()) llvm_global.setDLLStorageClass(.DLLExport); if (self.di_map.get(decl)) |di_node| { if (try decl.isFunction()) { const di_func = @ptrCast(*llvm.DISubprogram, di_node); @@ -1187,6 +1189,7 @@ pub const Object = struct { defer module.gpa.free(fqn); llvm_global.setValueName2(fqn.ptr, fqn.len); llvm_global.setLinkage(.Internal); + if (module.wantDllExports()) llvm_global.setDLLStorageClass(.Default); llvm_global.setUnnamedAddr(.True); if (decl.val.castTag(.variable)) |variable| { const single_threaded = module.comp.bin_file.options.single_threaded; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 9daa96eb8f..38f794cfda 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -223,6 +223,9 @@ pub const Value = opaque { pub const setInitializer = LLVMSetInitializer; extern fn LLVMSetInitializer(GlobalVar: *const Value, ConstantVal: *const Value) void; + pub const setDLLStorageClass = LLVMSetDLLStorageClass; + extern fn LLVMSetDLLStorageClass(Global: *const Value, Class: DLLStorageClass) void; + pub const addCase = LLVMAddCase; extern fn LLVMAddCase(Switch: *const Value, OnVal: *const Value, Dest: *const BasicBlock) void; @@ -1482,6 +1485,12 @@ pub const CallAttr = enum(c_int) { AlwaysInline, }; +pub const DLLStorageClass = enum(c_uint) { + Default, + DLLImport, + DLLExport, +}; + pub const address_space = struct { pub const default: c_uint = 0; -- cgit v1.2.3 From d48af541c7aa235948621cdbc250d983af303977 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Sun, 21 Aug 2022 12:25:19 +0300 Subject: Sema: handle union and enum field order being different Closes #12543 --- src/Sema.zig | 33 ++++++++++++++++++--------------- src/codegen.zig | 2 +- src/codegen/c.zig | 3 +-- src/codegen/llvm.zig | 2 +- src/type.zig | 9 ++++++++- test/behavior/union.zig | 24 ++++++++++++++++++++++++ 6 files changed, 53 insertions(+), 20 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 5c66c8d6a1..28ff4cc1c2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -3615,8 +3615,6 @@ fn validateUnionInit( union_ptr: Air.Inst.Ref, is_comptime: bool, ) CompileError!void { - const union_obj = union_ty.cast(Type.Payload.Union).?.data; - if (instrs.len != 1) { const msg = msg: { const msg = try sema.errMsg( @@ -3650,7 +3648,8 @@ fn validateUnionInit( const field_src: LazySrcLoc = .{ .node_offset_initializer = field_ptr_data.src_node }; const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); - const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src); + // Validate the field access but ignore the index since we want the tag enum field index. + _ = try sema.unionFieldIndex(block, union_ty, field_name, field_src); const air_tags = sema.air_instructions.items(.tag); const air_datas = sema.air_instructions.items(.data); const field_ptr_air_ref = sema.inst_map.get(field_ptr).?; @@ -3709,7 +3708,9 @@ fn validateUnionInit( break; } - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const tag_ty = union_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); if (init_val) |val| { // Our task is to delete all the `field_ptr` and `store` instructions, and insert @@ -3726,7 +3727,7 @@ fn validateUnionInit( } try sema.requireFunctionBlock(block, init_src); - const new_tag = try sema.addConstant(union_obj.tag_ty, tag_val); + const new_tag = try sema.addConstant(tag_ty, tag_val); _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag); } @@ -8838,13 +8839,11 @@ fn zirSwitchCapture( switch (operand_ty.zigTypeTag()) { .Union => { const union_obj = operand_ty.cast(Type.Payload.Union).?.data; - const enum_ty = union_obj.tag_ty; - const first_item = try sema.resolveInst(items[0]); // Previous switch validation ensured this will succeed const first_item_val = sema.resolveConstValue(block, .unneeded, first_item, undefined) catch unreachable; - const first_field_index = @intCast(u32, enum_ty.enumTagFieldIndex(first_item_val, sema.mod).?); + const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, sema.mod).?); const first_field = union_obj.fields.values()[first_field_index]; for (items[1..]) |item, i| { @@ -8852,7 +8851,7 @@ fn zirSwitchCapture( // Previous switch validation ensured this will succeed const item_val = sema.resolveConstValue(block, .unneeded, item_ref, undefined) catch unreachable; - const field_index = enum_ty.enumTagFieldIndex(item_val, sema.mod).?; + const field_index = operand_ty.unionTagFieldIndex(item_val, sema.mod).?; const field = union_obj.fields.values()[field_index]; if (!field.ty.eql(first_field.ty, sema.mod)) { const msg = msg: { @@ -15585,7 +15584,9 @@ fn unionInit( const init = try sema.coerce(block, field.ty, uncasted_init, init_src); if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const tag_ty = union_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); return sema.addConstant(union_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = init_val, @@ -15683,7 +15684,9 @@ fn zirStructInit( const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src); - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + const tag_ty = resolved_ty.unionTagTypeHypothetical(); + const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name).?); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, enum_field_index); const init_inst = try sema.resolveInst(item.data.init); if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { @@ -16448,9 +16451,8 @@ fn zirReify(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, in const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src); const val = try sema.resolveConstValue(block, operand_src, type_info, "operand to @Type must be comptime known"); const union_val = val.cast(Value.Payload.Union).?.data; - const tag_ty = type_info_ty.unionTagType().?; const target = mod.getTarget(); - const tag_index = tag_ty.enumTagFieldIndex(union_val.tag, mod).?; + const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag, mod).?; if (union_val.val.anyUndef()) return sema.failWithUseOfUndef(block, src); switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, @@ -25155,8 +25157,7 @@ fn coerceEnumToUnion( const enum_tag = try sema.coerce(block, tag_ty, inst, inst_src); if (try sema.resolveDefinedValue(block, inst_src, enum_tag)) |val| { - const union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.tag_ty.enumTagFieldIndex(val, sema.mod) orelse { + const field_index = union_ty.unionTagFieldIndex(val, sema.mod) orelse { const msg = msg: { const msg = try sema.errMsg(block, inst_src, "union '{}' has no tag with value '{}'", .{ union_ty.fmt(sema.mod), val.fmtValue(tag_ty, sema.mod), @@ -25167,6 +25168,8 @@ fn coerceEnumToUnion( }; return sema.failWithOwnedErrorMsg(msg); }; + + const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field = union_obj.fields.values()[field_index]; const field_ty = try sema.resolveTypeFields(block, inst_src, field.ty); if (field_ty.zigTypeTag() == .NoReturn) { diff --git a/src/codegen.zig b/src/codegen.zig index 025decdb4b..f5340458a5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -607,7 +607,7 @@ pub fn generateSymbol( const union_ty = typed_value.ty.cast(Type.Payload.Union).?.data; const mod = bin_file.options.module.?; - const field_index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag, mod).?; + const field_index = typed_value.ty.unionTagFieldIndex(union_obj.tag, mod).?; assert(union_ty.haveFieldTypes()); const field_ty = union_ty.fields.values()[field_index].ty; if (!field_ty.hasRuntimeBits()) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 81a892183f..4a09c09cc9 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -835,7 +835,6 @@ pub const DeclGen = struct { }, .Union => { const union_obj = val.castTag(.@"union").?.data; - const union_ty = ty.cast(Type.Payload.Union).?.data; const layout = ty.unionGetLayout(target); try writer.writeAll("("); @@ -851,7 +850,7 @@ pub const DeclGen = struct { try writer.writeAll(".payload = {"); } - const index = union_ty.tag_ty.enumTagFieldIndex(union_obj.tag, dg.module).?; + const index = ty.unionTagFieldIndex(union_obj.tag, dg.module).?; const field_ty = ty.unionFields().values()[index].ty; const field_name = ty.unionFields().keys()[index]; if (field_ty.hasRuntimeBits()) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d50b463606..5c537cd5bc 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3502,7 +3502,7 @@ pub const DeclGen = struct { }); } const union_obj = tv.ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.tag_ty.enumTagFieldIndex(tag_and_val.tag, dg.module).?; + const field_index = tv.ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?; assert(union_obj.haveFieldTypes()); // Sometimes we must make an unnamed struct because LLVM does diff --git a/src/type.zig b/src/type.zig index f6afa33df1..d2885f537f 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4285,11 +4285,18 @@ pub const Type = extern union { pub fn unionFieldType(ty: Type, enum_tag: Value, mod: *Module) Type { const union_obj = ty.cast(Payload.Union).?.data; - const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag, mod).?; + const index = ty.unionTagFieldIndex(enum_tag, mod).?; assert(union_obj.haveFieldTypes()); return union_obj.fields.values()[index].ty; } + pub fn unionTagFieldIndex(ty: Type, enum_tag: Value, mod: *Module) ?usize { + const union_obj = ty.cast(Payload.Union).?.data; + const index = union_obj.tag_ty.enumTagFieldIndex(enum_tag, mod) orelse return null; + const name = union_obj.tag_ty.enumFieldName(index); + return union_obj.fields.getIndex(name); + } + pub fn unionHasAllZeroBitFieldTypes(ty: Type) bool { return ty.cast(Payload.Union).?.data.hasAllZeroBitFieldTypes(); } diff --git a/test/behavior/union.zig b/test/behavior/union.zig index efca75af30..92f277b946 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1301,3 +1301,27 @@ test "noreturn field in union" { } try expect(count == 5); } + +test "union and enum field order doesn't match" { + if (builtin.zig_backend == .stage1) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + + const MyTag = enum(u32) { + b = 1337, + a = 1666, + }; + const MyUnion = union(MyTag) { + a: f32, + b: void, + }; + var x: MyUnion = .{ .a = 666 }; + switch (x) { + .a => |my_f32| { + try expect(@TypeOf(my_f32) == f32); + }, + .b => unreachable, + } + x = .b; + try expect(x == .b); +} -- cgit v1.2.3 From b0bcd4add29b6fbbe2bbc22719703d3c81ed594b Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Mon, 22 Aug 2022 10:17:58 +0300 Subject: Sema: allow optional pointers in packed structs Closes #12572 --- src/Sema.zig | 2 +- src/codegen/llvm.zig | 9 ++++++--- test/behavior/packed-struct.zig | 12 ++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/Sema.zig b/src/Sema.zig index 2355e8374d..9538073e5c 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -20565,8 +20565,8 @@ fn validatePackedType(ty: Type) bool { .AnyFrame, .Fn, .Array, - .Optional, => return false, + .Optional => return ty.isPtrLikeOptional(), .Void, .Bool, .Float, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 5c537cd5bc..98458854d1 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -3417,7 +3417,10 @@ pub const DeclGen = struct { }); const ty_bit_size = @intCast(u16, field.ty.bitSize(target)); const small_int_ty = dg.context.intType(ty_bit_size); - const small_int_val = non_int_val.constBitCast(small_int_ty); + const small_int_val = if (field.ty.isPtrAtRuntime()) + non_int_val.constPtrToInt(small_int_ty) + else + non_int_val.constBitCast(small_int_ty); const shift_rhs = int_llvm_ty.constInt(running_bits, .False); // If the field is as large as the entire packed struct, this // zext would go from, e.g. i16 to i16. This is legal with @@ -5343,7 +5346,7 @@ pub const FuncGen = struct { const same_size_int = self.context.intType(elem_bits); const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, ""); return self.builder.buildBitCast(truncated_int, elem_llvm_ty, ""); - } else if (field_ty.zigTypeTag() == .Pointer) { + } else if (field_ty.isPtrAtRuntime()) { const elem_bits = @intCast(c_uint, field_ty.bitSize(target)); const same_size_int = self.context.intType(elem_bits); const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, ""); @@ -8408,7 +8411,7 @@ pub const FuncGen = struct { const non_int_val = try self.resolveInst(elem); const ty_bit_size = @intCast(u16, field.ty.bitSize(target)); const small_int_ty = self.dg.context.intType(ty_bit_size); - const small_int_val = if (field.ty.zigTypeTag() == .Pointer) + const small_int_val = if (field.ty.isPtrAtRuntime()) self.builder.buildPtrToInt(non_int_val, small_int_ty, "") else self.builder.buildBitCast(non_int_val, small_int_ty, ""); diff --git a/test/behavior/packed-struct.zig b/test/behavior/packed-struct.zig index 8c34f5741b..540914bd25 100644 --- a/test/behavior/packed-struct.zig +++ b/test/behavior/packed-struct.zig @@ -434,3 +434,15 @@ test "@ptrToInt on a packed struct field" { }; try expect(@ptrToInt(&S.p0.z) - @ptrToInt(&S.p0.x) == 2); } + +test "optional pointer in packed struct" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + + const T = packed struct { ptr: ?*const u8 }; + var n: u8 = 0; + const x = T{ .ptr = &n }; + try expect(x.ptr.? == &n); +} -- cgit v1.2.3 From dae7aeb33779d0214c3efe0960af0059416aed51 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 22 Aug 2022 16:04:07 -0700 Subject: llvm: add valgrind client request integration for x86_64 closes #12500 --- src/codegen/llvm.zig | 92 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 4 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 98458854d1..cddbdc6822 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4019,6 +4019,9 @@ pub const FuncGen = struct { /// Note that this can disagree with isByRef for the return type in the case /// of C ABI functions. ret_ptr: ?*const llvm.Value, + /// Any function that needs to perform Valgrind client requests needs an array alloca + /// instruction, however a maximum of one per function is needed. + valgrind_client_request_array: ?*const llvm.Value = null, /// These fields are used to refer to the LLVM value of the function parameters /// in an Arg instruction. /// This list may be shorter than the list according to the zig type system; @@ -7530,8 +7533,7 @@ pub const FuncGen = struct { const len = usize_llvm_ty.constInt(operand_size, .False); _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr()); if (self.dg.module.comp.bin_file.options.valgrind) { - // TODO generate valgrind client request to mark byte range as undefined - // see gen_valgrind_undef() in codegen.cpp + self.valgrindMarkUndef(dest_ptr, len); } } else { const src_operand = try self.resolveInst(bin_op.rhs); @@ -7790,8 +7792,7 @@ pub const FuncGen = struct { _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr()); if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) { - // TODO generate valgrind client request to mark byte range as undefined - // see gen_valgrind_undef() in codegen.cpp + self.valgrindMarkUndef(dest_ptr_u8, len); } return null; } @@ -9098,6 +9099,89 @@ pub const FuncGen = struct { info.@"volatile", ); } + + fn valgrindMarkUndef(fg: *FuncGen, ptr: *const llvm.Value, len: *const llvm.Value) void { + const VG_USERREQ__MAKE_MEM_UNDEFINED = 1296236545; + const target = fg.dg.module.getTarget(); + const usize_llvm_ty = fg.context.intType(target.cpu.arch.ptrBitWidth()); + const zero = usize_llvm_ty.constInt(0, .False); + const req = usize_llvm_ty.constInt(VG_USERREQ__MAKE_MEM_UNDEFINED, .False); + const ptr_as_usize = fg.builder.buildPtrToInt(ptr, usize_llvm_ty, ""); + _ = valgrindClientRequest(fg, zero, req, ptr_as_usize, len, zero, zero, zero); + } + + fn valgrindClientRequest( + fg: *FuncGen, + default_value: *const llvm.Value, + request: *const llvm.Value, + a1: *const llvm.Value, + a2: *const llvm.Value, + a3: *const llvm.Value, + a4: *const llvm.Value, + a5: *const llvm.Value, + ) *const llvm.Value { + const target = fg.dg.module.getTarget(); + if (!target_util.hasValgrindSupport(target)) return default_value; + + const usize_llvm_ty = fg.context.intType(target.cpu.arch.ptrBitWidth()); + const usize_alignment = @intCast(c_uint, Type.usize.abiSize(target)); + + switch (target.cpu.arch) { + .x86_64 => { + const array_ptr = fg.valgrind_client_request_array orelse a: { + const array_ptr = fg.buildAlloca(usize_llvm_ty.arrayType(6)); + array_ptr.setAlignment(usize_alignment); + fg.valgrind_client_request_array = array_ptr; + break :a array_ptr; + }; + const array_elements = [_]*const llvm.Value{ request, a1, a2, a3, a4, a5 }; + const zero = usize_llvm_ty.constInt(0, .False); + for (array_elements) |elem, i| { + const indexes = [_]*const llvm.Value{ + zero, usize_llvm_ty.constInt(@intCast(c_uint, i), .False), + }; + const elem_ptr = fg.builder.buildInBoundsGEP(array_ptr, &indexes, indexes.len, ""); + const store_inst = fg.builder.buildStore(elem, elem_ptr); + store_inst.setAlignment(usize_alignment); + } + + const asm_template = + \\rolq $$3, %rdi ; rolq $$13, %rdi + \\rolq $$61, %rdi ; rolq $$51, %rdi + \\xchgq %rbx,%rbx + ; + + const asm_constraints = "={rdx},{rax},0,~{cc},~{memory}"; + + const array_ptr_as_usize = fg.builder.buildPtrToInt(array_ptr, usize_llvm_ty, ""); + const args = [_]*const llvm.Value{ array_ptr_as_usize, default_value }; + const param_types = [_]*const llvm.Type{ usize_llvm_ty, usize_llvm_ty }; + const fn_llvm_ty = llvm.functionType(usize_llvm_ty, ¶m_types, args.len, .False); + const asm_fn = llvm.getInlineAsm( + fn_llvm_ty, + asm_template, + asm_template.len, + asm_constraints, + asm_constraints.len, + .True, // has side effects + .False, // alignstack + .ATT, + .False, + ); + + const call = fg.builder.buildCall( + asm_fn, + &args, + args.len, + .C, + .Auto, + "", + ); + return call; + }, + else => unreachable, + } + } }; fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void { -- cgit v1.2.3 From 7453f56e678c80928ababa2868c69cfe41647fed Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 24 Aug 2022 20:27:11 -0700 Subject: stage2: explicitly tagged enums no longer have one possible value Previously, Zig had inconsistent semantics for an enum like this: `enum(u8){zero = 0}` Although in theory this can only hold one possible value, the tag `zero`, Zig no longer will treat the type this way. It will do loads and stores, as if the type has runtime bits. Closes #12619 Tests passed locally: * test-behavior * test-cases --- lib/std/elf.zig | 14 ++++++------- src/Module.zig | 26 ++++++++++++----------- src/Sema.zig | 7 +++++++ src/arch/x86_64/CodeGen.zig | 4 ++-- src/codegen/llvm.zig | 8 +++---- src/type.zig | 51 +++++++++++++++++++++++++++++---------------- test/behavior.zig | 1 - test/behavior/bugs/1111.zig | 11 ---------- test/behavior/enum.zig | 42 +++++++++++++++++++++++++++++++++++++ test/behavior/union.zig | 9 ++++---- 10 files changed, 113 insertions(+), 60 deletions(-) delete mode 100644 test/behavior/bugs/1111.zig (limited to 'src/codegen/llvm.zig') diff --git a/lib/std/elf.zig b/lib/std/elf.zig index 16581b7782..cc43e11a7d 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -3,7 +3,7 @@ const io = std.io; const os = std.os; const math = std.math; const mem = std.mem; -const debug = std.debug; +const assert = std.debug.assert; const File = std.fs.File; const native_endian = @import("builtin").target.cpu.arch.endian(); @@ -872,14 +872,14 @@ pub const Elf_MIPS_ABIFlags_v0 = extern struct { }; comptime { - debug.assert(@sizeOf(Elf32_Ehdr) == 52); - debug.assert(@sizeOf(Elf64_Ehdr) == 64); + assert(@sizeOf(Elf32_Ehdr) == 52); + assert(@sizeOf(Elf64_Ehdr) == 64); - debug.assert(@sizeOf(Elf32_Phdr) == 32); - debug.assert(@sizeOf(Elf64_Phdr) == 56); + assert(@sizeOf(Elf32_Phdr) == 32); + assert(@sizeOf(Elf64_Phdr) == 56); - debug.assert(@sizeOf(Elf32_Shdr) == 40); - debug.assert(@sizeOf(Elf64_Shdr) == 64); + assert(@sizeOf(Elf32_Shdr) == 40); + assert(@sizeOf(Elf64_Shdr) == 64); } pub const Auxv = switch (@sizeOf(usize)) { diff --git a/src/Module.zig b/src/Module.zig index 34617ed3e2..a92849e127 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -1368,18 +1368,20 @@ pub const Union = struct { } } payload_align = @maximum(payload_align, 1); - if (!have_tag or fields.len <= 1) return .{ - .abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align), - .abi_align = payload_align, - .most_aligned_field = most_aligned_field, - .most_aligned_field_size = most_aligned_field_size, - .biggest_field = biggest_field, - .payload_size = payload_size, - .payload_align = payload_align, - .tag_align = 0, - .tag_size = 0, - .padding = 0, - }; + if (!have_tag or !u.tag_ty.hasRuntimeBits()) { + return .{ + .abi_size = std.mem.alignForwardGeneric(u64, payload_size, payload_align), + .abi_align = payload_align, + .most_aligned_field = most_aligned_field, + .most_aligned_field_size = most_aligned_field_size, + .biggest_field = biggest_field, + .payload_size = payload_size, + .payload_align = payload_align, + .tag_align = 0, + .tag_size = 0, + .padding = 0, + }; + } // Put the tag before or after the payload depending on which one's // alignment is greater. const tag_size = u.tag_ty.abiSize(target); diff --git a/src/Sema.zig b/src/Sema.zig index b24a00150a..3e8005269f 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -28844,6 +28844,10 @@ pub fn typeHasOnePossibleValue( .enum_numbered => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const enum_obj = resolved_ty.castTag(.enum_numbered).?.data; + // An explicit tag type is always provided for enum_numbered. + if (enum_obj.tag_ty.hasRuntimeBits()) { + return null; + } if (enum_obj.fields.count() == 1) { if (enum_obj.values.count() == 0) { return Value.zero; // auto-numbered @@ -28857,6 +28861,9 @@ pub fn typeHasOnePossibleValue( .enum_full => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const enum_obj = resolved_ty.castTag(.enum_full).?.data; + if (enum_obj.tag_ty.hasRuntimeBits()) { + return null; + } if (enum_obj.fields.count() == 1) { if (enum_obj.values.count() == 0) { return Value.zero; // auto-numbered diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 0f6b6baee3..106d2feec0 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -6524,13 +6524,13 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { const extra = self.air.extraData(Air.Block, ty_pl.payload); _ = ty_pl; _ = extra; - return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); + return self.fail("TODO implement x86 airCmpxchg", .{}); // return self.finishAir(inst, result, .{ extra.ptr, extra.expected_value, extra.new_value }); } fn airAtomicRmw(self: *Self, inst: Air.Inst.Index) !void { _ = inst; - return self.fail("TODO implement airCmpxchg for {}", .{self.target.cpu.arch}); + return self.fail("TODO implement x86 airAtomicRaw", .{}); } fn airAtomicLoad(self: *Self, inst: Air.Inst.Index) !void { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index cddbdc6822..d872057beb 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1860,7 +1860,7 @@ pub const Object = struct { var offset: u64 = 0; for (fields.values()) |field, i| { - if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; const field_size = field.ty.abiSize(target); const field_align = field.alignment(target, layout); @@ -2764,7 +2764,7 @@ pub const DeclGen = struct { var any_underaligned_fields = false; for (struct_obj.fields.values()) |field| { - if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; const field_align = field.alignment(target, struct_obj.layout); const field_ty_align = field.ty.abiAlignment(target); @@ -3443,7 +3443,7 @@ pub const DeclGen = struct { var need_unnamed = false; for (struct_obj.fields.values()) |field, i| { - if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; const field_align = field.alignment(target, struct_obj.layout); big_align = @maximum(big_align, field_align); @@ -9477,7 +9477,7 @@ fn llvmFieldIndex( var llvm_field_index: c_uint = 0; for (ty.structFields().values()) |field, i| { - if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime()) continue; + if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; const field_align = field.alignment(target, layout); big_align = @maximum(big_align, field_align); diff --git a/src/type.zig b/src/type.zig index 1592dcf469..cc6e5706ee 100644 --- a/src/type.zig +++ b/src/type.zig @@ -2310,6 +2310,8 @@ pub const Type = extern union { /// fields will count towards the ABI size. For example, `struct {T: type, x: i32}` /// hasRuntimeBits()=true and abiSize()=4 /// * the type has only one possible value, making its ABI size 0. + /// - an enum with an explicit tag type has the ABI size of the integer tag type, + /// making it one-possible-value only if the integer tag type has 0 bits. /// When `ignore_comptime_only` is true, then types that are comptime only /// may return false positives. pub fn hasRuntimeBitsAdvanced( @@ -2452,9 +2454,9 @@ pub const Type = extern union { _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); } assert(struct_obj.haveFieldTypes()); - for (struct_obj.fields.values()) |value| { - if (value.is_comptime) continue; - if (try value.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) + for (struct_obj.fields.values()) |field| { + if (field.is_comptime) continue; + if (try field.ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) return true; } else { return false; @@ -2463,7 +2465,7 @@ pub const Type = extern union { .enum_full => { const enum_full = ty.castTag(.enum_full).?.data; - return enum_full.fields.count() >= 2; + return enum_full.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit); }, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; @@ -2490,9 +2492,10 @@ pub const Type = extern union { }, .union_safety_tagged, .union_tagged => { const union_obj = ty.cast(Payload.Union).?.data; - if (union_obj.fields.count() > 0 and try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { + if (try union_obj.tag_ty.hasRuntimeBitsAdvanced(ignore_comptime_only, sema_kit)) { return true; } + if (sema_kit) |sk| { _ = try sk.sema.resolveTypeFields(sk.block, sk.src, ty); } @@ -3125,7 +3128,11 @@ pub const Type = extern union { .lazy => |arena| return AbiAlignmentAdvanced{ .val = try Value.Tag.lazy_align.create(arena, ty) }, }; if (union_obj.fields.count() == 0) { - return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) }; + if (have_tag) { + return abiAlignmentAdvanced(union_obj.tag_ty, target, strat); + } else { + return AbiAlignmentAdvanced{ .scalar = @boolToInt(union_obj.layout == .Extern) }; + } } var max_align: u32 = 0; @@ -4991,14 +4998,18 @@ pub const Type = extern union { .enum_numbered => { const enum_numbered = ty.castTag(.enum_numbered).?.data; - if (enum_numbered.fields.count() == 1) { - return enum_numbered.values.keys()[0]; - } else { + // An explicit tag type is always provided for enum_numbered. + if (enum_numbered.tag_ty.hasRuntimeBits()) { return null; } + assert(enum_numbered.fields.count() == 1); + return enum_numbered.values.keys()[0]; }, .enum_full => { const enum_full = ty.castTag(.enum_full).?.data; + if (enum_full.tag_ty.hasRuntimeBits()) { + return null; + } if (enum_full.fields.count() == 1) { if (enum_full.values.count() == 0) { return Value.zero; @@ -5333,7 +5344,8 @@ pub const Type = extern union { .enum_numbered => return ty.castTag(.enum_numbered).?.data.tag_ty, .enum_simple => { const enum_simple = ty.castTag(.enum_simple).?.data; - const bits = std.math.log2_int_ceil(usize, enum_simple.fields.count()); + const field_count = enum_simple.fields.count(); + const bits: u16 = if (field_count == 0) 0 else std.math.log2_int_ceil(usize, field_count); buffer.* = .{ .base = .{ .tag = .int_unsigned }, .data = bits, @@ -5653,19 +5665,22 @@ pub const Type = extern union { target: Target, pub fn next(it: *StructOffsetIterator) ?FieldOffset { - if (it.struct_obj.fields.count() <= it.field) + const i = it.field; + if (it.struct_obj.fields.count() <= i) return null; - const field = it.struct_obj.fields.values()[it.field]; - defer it.field += 1; - if (!field.ty.hasRuntimeBits() or field.is_comptime) - return FieldOffset{ .field = it.field, .offset = it.offset }; + const field = it.struct_obj.fields.values()[i]; + it.field += 1; + + if (field.is_comptime or !field.ty.hasRuntimeBits()) { + return FieldOffset{ .field = i, .offset = it.offset }; + } const field_align = field.alignment(it.target, it.struct_obj.layout); it.big_align = @maximum(it.big_align, field_align); - it.offset = std.mem.alignForwardGeneric(u64, it.offset, field_align); - defer it.offset += field.ty.abiSize(it.target); - return FieldOffset{ .field = it.field, .offset = it.offset }; + const field_offset = std.mem.alignForwardGeneric(u64, it.offset, field_align); + it.offset = field_offset + field.ty.abiSize(it.target); + return FieldOffset{ .field = i, .offset = field_offset }; } }; diff --git a/test/behavior.zig b/test/behavior.zig index ba8379cd72..12edd6f9a3 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -26,7 +26,6 @@ test { _ = @import("behavior/bugs/920.zig"); _ = @import("behavior/bugs/1025.zig"); _ = @import("behavior/bugs/1076.zig"); - _ = @import("behavior/bugs/1111.zig"); _ = @import("behavior/bugs/1277.zig"); _ = @import("behavior/bugs/1310.zig"); _ = @import("behavior/bugs/1381.zig"); diff --git a/test/behavior/bugs/1111.zig b/test/behavior/bugs/1111.zig deleted file mode 100644 index d274befaf3..0000000000 --- a/test/behavior/bugs/1111.zig +++ /dev/null @@ -1,11 +0,0 @@ -const Foo = enum(c_int) { - Bar = -1, -}; - -test "issue 1111 fixed" { - const v = Foo.Bar; - - switch (v) { - Foo.Bar => return, - } -} diff --git a/test/behavior/enum.zig b/test/behavior/enum.zig index 709d30af33..9e96163cc0 100644 --- a/test/behavior/enum.zig +++ b/test/behavior/enum.zig @@ -1,6 +1,7 @@ const builtin = @import("builtin"); const std = @import("std"); const expect = std.testing.expect; +const assert = std.debug.assert; const mem = std.mem; const Tag = std.meta.Tag; @@ -1128,3 +1129,44 @@ test "tag name functions are unique" { _ = a; } } + +test "size of enum with only one tag which has explicit integer tag type" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + + const E = enum(u8) { nope = 10 }; + const S0 = struct { e: E }; + const S1 = extern struct { e: E }; + //const U = union(E) { nope: void }; + comptime assert(@sizeOf(E) == 1); + comptime assert(@sizeOf(S0) == 1); + comptime assert(@sizeOf(S1) == 1); + //comptime assert(@sizeOf(U) == 1); + + var s1: S1 = undefined; + s1.e = .nope; + try expect(s1.e == .nope); + const ptr = @ptrCast(*u8, &s1); + try expect(ptr.* == 10); + + var s0: S0 = undefined; + s0.e = .nope; + try expect(s0.e == .nope); +} + +test "switch on an extern enum with negative value" { + // TODO x86, wasm backends fail because they assume that enum tag types are unsigned + if (@import("builtin").zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (@import("builtin").zig_backend == .stage2_wasm) return error.SkipZigTest; + + const Foo = enum(c_int) { + Bar = -1, + }; + + const v = Foo.Bar; + + switch (v) { + Foo.Bar => return, + } +} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 92f277b946..e2078c66df 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1,6 +1,7 @@ const builtin = @import("builtin"); const std = @import("std"); const expect = std.testing.expect; +const assert = std.debug.assert; const expectEqual = std.testing.expectEqual; const Tag = std.meta.Tag; @@ -1065,6 +1066,8 @@ test "@unionInit on union with tag but no fields" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO const S = struct { const Type = enum(u8) { no_op = 105 }; @@ -1079,11 +1082,7 @@ test "@unionInit on union with tag but no fields" { }; comptime { - if (builtin.zig_backend == .stage1) { - // stage1 gets the wrong answer here - } else { - std.debug.assert(@sizeOf(Data) == 0); - } + assert(@sizeOf(Data) == 1); } fn doTheTest() !void { -- cgit v1.2.3 From d2ad8afff4c404f6e1a566cce3fa6e7f768503e5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 25 Aug 2022 16:10:36 -0700 Subject: LLVM: fix missing alignment on wrapping instructions Previously, when lowering AIR instructions `wrap_errunion_payload`, `wrap_errunion_err`, and `wrap_optional`, the LLVM backend would create an alloca instruction to store the result, but did not set the alignment on it. This caused UB which went undetected for a long time until we started enabling the stack protector. Closes #12594 Unblocks #12508 Inspires #12634 Tests passed locally: * test-behavior * test-cases --- src/codegen/llvm.zig | 10 +++++++--- test/behavior/error.zig | 16 ++++++++++++++++ test/behavior/optional.zig | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) (limited to 'src/codegen/llvm.zig') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d872057beb..d1c68b430c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -6188,7 +6188,9 @@ pub const FuncGen = struct { } const llvm_optional_ty = try self.dg.lowerType(optional_ty); if (isByRef(optional_ty)) { + const target = self.dg.module.getTarget(); const optional_ptr = self.buildAlloca(llvm_optional_ty); + optional_ptr.setAlignment(optional_ty.abiAlignment(target)); const payload_ptr = self.builder.buildStructGEP(optional_ptr, 0, ""); var ptr_ty_payload: Type.Payload.ElemType = .{ .base = .{ .tag = .single_mut_pointer }, @@ -6208,20 +6210,21 @@ pub const FuncGen = struct { if (self.liveness.isUnused(inst)) return null; const ty_op = self.air.instructions.items(.data)[inst].ty_op; - const inst_ty = self.air.typeOfIndex(inst); + const err_un_ty = self.air.typeOfIndex(inst); const operand = try self.resolveInst(ty_op.operand); const payload_ty = self.air.typeOf(ty_op.operand); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { return operand; } const ok_err_code = (try self.dg.lowerType(Type.anyerror)).constNull(); - const err_un_llvm_ty = try self.dg.lowerType(inst_ty); + const err_un_llvm_ty = try self.dg.lowerType(err_un_ty); const target = self.dg.module.getTarget(); const payload_offset = errUnionPayloadOffset(payload_ty, target); const error_offset = errUnionErrorOffset(payload_ty, target); - if (isByRef(inst_ty)) { + if (isByRef(err_un_ty)) { const result_ptr = self.buildAlloca(err_un_llvm_ty); + result_ptr.setAlignment(err_un_ty.abiAlignment(target)); const err_ptr = self.builder.buildStructGEP(result_ptr, error_offset, ""); const store_inst = self.builder.buildStore(ok_err_code, err_ptr); store_inst.setAlignment(Type.anyerror.abiAlignment(target)); @@ -6256,6 +6259,7 @@ pub const FuncGen = struct { const error_offset = errUnionErrorOffset(payload_ty, target); if (isByRef(err_un_ty)) { const result_ptr = self.buildAlloca(err_un_llvm_ty); + result_ptr.setAlignment(err_un_ty.abiAlignment(target)); const err_ptr = self.builder.buildStructGEP(result_ptr, error_offset, ""); const store_inst = self.builder.buildStore(operand, err_ptr); store_inst.setAlignment(Type.anyerror.abiAlignment(target)); diff --git a/test/behavior/error.zig b/test/behavior/error.zig index b355c85819..684b01a797 100644 --- a/test/behavior/error.zig +++ b/test/behavior/error.zig @@ -796,3 +796,19 @@ test "error union of noreturn used with catch" { const err = NoReturn.testCatch(); try expect(err == error.OtherFailure); } + +test "alignment of wrapping an error union payload" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + + const S = struct { + const I = extern struct { x: i128 }; + + fn foo() anyerror!I { + var i: I = .{ .x = 1234 }; + return i; + } + }; + try expect((S.foo() catch unreachable).x == 1234); +} diff --git a/test/behavior/optional.zig b/test/behavior/optional.zig index 28261daf1f..eb693147e6 100644 --- a/test/behavior/optional.zig +++ b/test/behavior/optional.zig @@ -412,3 +412,19 @@ test "orelse on C pointer" { const d = foo orelse @compileError("bad"); try expectEqual([*c]const u8, @TypeOf(d)); } + +test "alignment of wrapping an optional payload" { + if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; + + const S = struct { + const I = extern struct { x: i128 }; + + fn foo() ?I { + var i: I = .{ .x = 1234 }; + return i; + } + }; + try expect(S.foo().?.x == 1234); +} -- cgit v1.2.3