stage2: implement @setAlignStack and 128-bit cmpxchg

* test runner is improved to respect `error.SkipZigTest`
 * start code is improved to `@setAlignStack(16)` before calling main()
 * the newly passing behavior test has a workaround for the fact that
   stage2 cannot yet call `std.Target.x86.featureSetHas()` at comptime.
   This is blocking on comptime closures. The workaround is that there
   is a new decl `@import("builtin").stage2_x86_cx16` which is a `bool`.
 * Implement `@setAlignStack`. This language feature should be re-evaluated
   at some point - I'll file an issue for it.
 * LLVM backend: apply/remove the cold attribute and noinline attribute
   where appropriate.
 * LLVM backend: loads and stores are properly annotated with alignment
   and volatile attributes.
 * LLVM backend: allocas are properly annotated with alignment.
 * Type: fix integers reporting wrong alignment for 256-bit integers and
   beyond. Once you get to 16 byte aligned, there is no further
   alignment for larger integers.
This commit is contained in:
Andrew Kelley
2021-09-16 21:03:55 -07:00
parent dc9d76b630
commit dbe9a5114e
12 changed files with 195 additions and 49 deletions

View File

@@ -355,6 +355,20 @@ pub const Object = struct {
const llvm_func = try dg.resolveLlvmFunction(decl);
if (module.align_stack_fns.get(func)) |align_info| {
dg.addFnAttrInt(llvm_func, "alignstack", align_info.alignment);
dg.addFnAttr(llvm_func, "noinline");
} else {
DeclGen.removeFnAttr(llvm_func, "alignstack");
if (!func.is_noinline) DeclGen.removeFnAttr(llvm_func, "noinline");
}
if (func.is_cold) {
dg.addFnAttr(llvm_func, "cold");
} else {
DeclGen.removeFnAttr(llvm_func, "cold");
}
// This gets the LLVM values from the function and stores them in `dg.args`.
const fn_param_len = decl.ty.fnParamLen();
var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len);
@@ -512,7 +526,9 @@ pub const DeclGen = struct {
}
}
/// If the llvm function does not exist, create it
/// If the llvm function does not exist, create it.
/// Note that this can be called before the function's semantic analysis has
/// completed, so if any attributes rely on that, they must be done in updateFunc, not here.
fn resolveLlvmFunction(self: *DeclGen, decl: *Module.Decl) !*const llvm.Value {
if (self.llvmModule().getNamedFunction(decl.name)) |llvm_fn| return llvm_fn;
@@ -895,17 +911,39 @@ pub const DeclGen = struct {
}
}
// Helper functions
fn addAttr(self: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
fn addAttr(dg: *DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
return dg.addAttrInt(val, index, name, 0);
}
fn removeAttr(val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
assert(kind_id != 0);
const llvm_attr = self.context.createEnumAttribute(kind_id, 0);
val.removeEnumAttributeAtIndex(index, kind_id);
}
fn addAttrInt(
dg: *DeclGen,
val: *const llvm.Value,
index: llvm.AttributeIndex,
name: []const u8,
int: u64,
) void {
const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
assert(kind_id != 0);
const llvm_attr = dg.context.createEnumAttribute(kind_id, int);
val.addAttributeAtIndex(index, llvm_attr);
}
fn addFnAttr(self: *DeclGen, val: *const llvm.Value, attr_name: []const u8) void {
// TODO: improve this API, `addAttr(-1, attr_name)`
self.addAttr(val, std.math.maxInt(llvm.AttributeIndex), attr_name);
fn addFnAttr(dg: *DeclGen, val: *const llvm.Value, name: []const u8) void {
dg.addAttr(val, std.math.maxInt(llvm.AttributeIndex), name);
}
fn removeFnAttr(fn_val: *const llvm.Value, name: []const u8) void {
removeAttr(fn_val, std.math.maxInt(llvm.AttributeIndex), name);
}
fn addFnAttrInt(dg: *DeclGen, fn_val: *const llvm.Value, name: []const u8, int: u64) void {
return dg.addAttrInt(fn_val, std.math.maxInt(llvm.AttributeIndex), name, int);
}
/// If the operand type of an atomic operation is not byte sized we need to
@@ -1975,12 +2013,13 @@ pub const FuncGen = struct {
return null;
// buildAlloca expects the pointee type, not the pointer type, so assert that
// a Payload.PointerSimple is passed to the alloc instruction.
const inst_ty = self.air.typeOfIndex(inst);
const pointee_type = inst_ty.castPointer().?.data;
// TODO: figure out a way to get the name of the var decl.
// TODO: set alignment and volatile
return self.buildAlloca(try self.dg.llvmType(pointee_type));
const ptr_ty = self.air.typeOfIndex(inst);
const pointee_type = ptr_ty.elemType();
const pointee_llvm_ty = try self.dg.llvmType(pointee_type);
const target = self.dg.module.getTarget();
const alloca_inst = self.buildAlloca(pointee_llvm_ty);
alloca_inst.setAlignment(ptr_ty.ptrAlignment(target));
return alloca_inst;
}
/// Use this instead of builder.buildAlloca, because this function makes sure to
@@ -2200,8 +2239,11 @@ pub const FuncGen = struct {
}
fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) *const llvm.Value {
_ = ptr_ty; // TODO set volatile and alignment on this load properly
return self.builder.buildLoad(ptr, "");
const llvm_inst = self.builder.buildLoad(ptr, "");
const target = self.dg.module.getTarget();
llvm_inst.setAlignment(ptr_ty.ptrAlignment(target));
llvm_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr()));
return llvm_inst;
}
fn store(
@@ -2210,8 +2252,11 @@ pub const FuncGen = struct {
ptr_ty: Type,
elem: *const llvm.Value,
) *const llvm.Value {
_ = ptr_ty; // TODO set volatile and alignment on this store properly
return self.builder.buildStore(elem, ptr);
const llvm_inst = self.builder.buildStore(elem, ptr);
const target = self.dg.module.getTarget();
llvm_inst.setAlignment(ptr_ty.ptrAlignment(target));
llvm_inst.setVolatile(llvm.Bool.fromBool(ptr_ty.isVolatilePtr()));
return llvm_inst;
}
};