diff --git a/Sources/LLVM/Function+Attributes.swift b/Sources/LLVM/Function+Attributes.swift new file mode 100644 index 00000000..47e74f0f --- /dev/null +++ b/Sources/LLVM/Function+Attributes.swift @@ -0,0 +1,385 @@ +#if SWIFT_PACKAGE +import cllvm +#endif + +/// Enumerates the kinds of attributes of LLVM functions and function parameters. +public enum AttributeKind: String { + /// This attribute indicates that, when emitting the prologue and epilogue, + /// the backend should forcibly align the stack pointer. + case alignstack + /// This attribute indicates that the annotated function will always return + /// at least a given number of bytes (or null). + case allocsize + /// This attribute indicates that the inliner should attempt to inline this + /// function into callers whenever possible, ignoring any active inlining + /// size threshold for this caller. + case alwaysinline + /// This indicates that the callee function at a call site should be + /// recognized as a built-in function, even though the function’s declaration + /// uses the nobuiltin attribute + case builtin + /// This attribute indicates that this function is rarely called. + case cold + /// In some parallel execution models, there exist operations that cannot be + /// made control-dependent on any additional values. + case convergent + /// This attribute indicates that the function may only access memory that is + /// not accessible by the module being compiled. + case inaccessiblememonly + /// This attribute indicates that the function may only access memory that is + /// either not accessible by the module being compiled, or is pointed to by + /// its pointer arguments. + case inaccessiblememOrArgmemonly = "inaccessiblemem_or_argmemonly" + /// This attribute indicates that the source code contained a hint that inlin + /// inlining this function is desirable (such as the “inline” keyword in + /// C/C++). + case inlinehint + /// This attribute indicates that the function should be added to a + /// jump-instruction table at code-generation time, and that all + /// address-taken references to this function should be replaced with a + /// reference to the appropriate jump-instruction-table function pointer. + case jumptable + /// This attribute suggests that optimization passes and code generator + /// passes make choices that keep the code size of this function as small as + /// possible and perform optimizations that may sacrifice runtime performance + /// in order to minimize the size of the generated code. + case minsize + /// This attribute disables prologue / epilogue emission for the function. + case naked + /// When this attribute is set to true, the jump tables and lookup tables + /// that can be generated from a switch case lowering are disabled. + case noJumpTables = "no-jump-tables" + /// This indicates that the callee function at a call site is not recognized + /// as a built-in function. + case nobuiltin + /// This attribute indicates that calls to the function cannot be duplicated. + case noduplicate + /// This attributes disables implicit floating point instructions. + case noimplicitfloat + /// This attribute indicates that the inliner should never inline this + /// function in any situation. + case noinline + /// This attribute suppresses lazy symbol binding for the function. + case nonlazybind + /// This attribute indicates that the code generator should not use a red + /// zone, even if the target-specific ABI normally permits it. + case noredzone + /// This function attribute indicates that the function never returns + /// normally. + case noreturn + /// This function attribute indicates that the function does not call itself + /// either directly or indirectly down any possible call path. + case norecurse + /// This function attribute indicates that the function never raises an + /// exception. + case nounwind + /// This function attribute indicates that most optimization passes will skip + /// this function, with the exception of interprocedural optimization passes. + case optnone + /// This attribute suggests that optimization passes and code generator + /// passes make choices that keep the code size of this function low, and + /// otherwise do optimizations specifically to reduce code size as long as + /// they do not significantly impact runtime performance. + case optsize + /// This attribute indicates that the function computes its result (or + /// decides to unwind an exception) based strictly on its arguments, without + /// dereferencing any pointer arguments or otherwise accessing any mutable + /// state (e.g. memory, control registers, etc) visible to caller functions. + case readnone + /// This attribute indicates that the function does not write through any + /// pointer arguments (including byval arguments) or otherwise modify any + /// state (e.g. memory, control registers, etc) visible to caller functions. + case readonly + /// This attribute indicates that the function may write to but does not read + /// read from memory. + case writeonly + /// This attribute indicates that the only memory accesses inside function + /// are loads and stores from objects pointed to by its pointer-typed + /// arguments, with arbitrary offsets. + case argmemonly + /// This attribute indicates that this function can return twice. + case returnsTwice = "returns_twice" + /// This attribute indicates that SafeStack protection is enabled for this + /// function. + case safestack + /// This attribute indicates that AddressSanitizer checks (dynamic address + /// safety analysis) are enabled for this function. + case sanitizeAddress = "sanitize_address" + /// This attribute indicates that MemorySanitizer checks (dynamic detection + /// of accesses to uninitialized memory) are enabled for this function. + case sanitizeMemory = "sanitize_memory" + /// This attribute indicates that ThreadSanitizer checks (dynamic thread + /// safety analysis) are enabled for this function. + case sanitizeThread = "sanitize_thread" + /// This attribute indicates that HWAddressSanitizer checks (dynamic address + /// safety analysis based on tagged pointers) are enabled for this function. + case sanitizeHWAddress = "sanitize_hwaddress" + /// This function attribute indicates that the function does not have any + /// effects besides calculating its result and does not have undefined + /// behavior. + case speculatable + /// This attribute indicates that the function should emit a stack smashing + /// protector. + case ssp + /// This attribute indicates that the function should always emit a stack + /// smashing protector + case sspreq + /// This attribute indicates that the function should emit a stack smashing + /// protector. + case sspstrong + /// This attribute indicates that the function was called from a scope that + /// requires strict floating point semantics. + case strictfp + /// This attribute indicates that the ABI being targeted requires that an + /// unwind table entry be produced for this function even if we can show + /// that no exceptions passes by it. + case uwtable + /// This indicates to the code generator that the parameter or return value + /// should be zero-extended to the extent required by the target’s ABI by the + /// caller (for a parameter) or the callee (for a return value). + case zeroext + /// This indicates to the code generator that the parameter or return value + /// should be sign-extended to the extent required by the target’s ABI (which + /// is usually 32-bits) by the caller (for a parameter) or the callee (for a + /// return value). + case signext + /// This indicates that this parameter or return value should be treated in a + /// special target-dependent fashion while emitting code for a function call + /// or return. + case inreg + /// This indicates that the pointer parameter should really be passed by + /// value to the function. + case byval + /// The inalloca argument attribute allows the caller to take the address of + /// outgoing stack arguments + case inalloca + /// This indicates that the pointer parameter specifies the address of a + /// structure that is the return value of the function in the source program. + case sret + /// This indicates that the pointer value may be assumed by the optimizer to + /// have the specified alignment. + case align + /// This indicates that objects accessed via pointer values based on the + /// argument or return value are not also accessed, during the execution of + /// the function, via pointer values not based on the argument or return + /// value. + case noalias + /// This indicates that the callee does not make any copies of the pointer + /// that outlive the callee itself. + case nocapture + /// This indicates that the pointer parameter can be excised using the + /// trampoline intrinsics. + case nest + /// This indicates that the function always returns the argument as its + /// return value. + case returned + /// This indicates that the parameter or return pointer is not null. + case nonnull + /// This indicates that the parameter or return pointer is dereferenceable. + case dereferenceable + /// This indicates that the parameter or return value isn’t both non-null and + /// non-dereferenceable (up to `n` bytes) at the same time. + case dereferenceableOrNull = "dereferenceable_or_null" + /// This indicates that the parameter is the self/context parameter. + case swiftself + /// This attribute is motivated to model and optimize Swift error handling. + case swifterror + + /// ID of the attribute. + internal var id: UInt32 { + return LLVMGetEnumAttributeKindForName(rawValue, rawValue.count) + } +} + +/// Represents the possible indices of function attributes. +public enum AttributeIndex: ExpressibleByIntegerLiteral, RawRepresentable { + /// Represents the function itself. + case function + /// Represents the function's return value. + case returnValue + /// Represents the function's i-th argument. + case argument(Int) + + public init(integerLiteral value: UInt32) { + switch value { + case ~0: self = .function + case 0: self = .returnValue + default: self = .argument(Int(value) - 1) + } + } + + public init?(rawValue: RawValue) { + self.init(integerLiteral: rawValue) + } + + public var rawValue: UInt32 { + switch self { + case .function: return ~0 + case .returnValue: return 0 + case .argument(let i): return UInt32(i) + 1 + } + } +} + +/// An LLVM attribute. +/// +/// Functions, return types and each parameter may have a set of attributes +/// associated with them. Such attributes are used to communicate additional +/// information about a function. Attributes are considered to be part of the +/// function, but not of the function type. +public protocol Attribute { + /// Retrieves the underlying LLVM attribute reference object. + func asLLVM() -> LLVMAttributeRef +} + +/// An "enum" (a.k.a. target-independent) attribute. +public struct EnumAttribute: Attribute { + internal let llvm: LLVMAttributeRef + internal init(llvm: LLVMAttributeRef) { + self.llvm = llvm + } + + /// The kind ID of the attribute. + internal var kindID: UInt32 { + return LLVMGetEnumAttributeKind(llvm) + } + + /// The value of the attribute. + public var value: UInt64 { + return LLVMGetEnumAttributeValue(llvm) + } + + /// Retrieves the underlying LLVM attribute object. + public func asLLVM() -> LLVMAttributeRef { + return llvm + } +} + +/// A "string" (a.k.a. target-dependent) attribute. +public struct StringAttribute: Attribute { + internal let llvm: LLVMAttributeRef + internal init(llvm: LLVMAttributeRef) { + self.llvm = llvm + } + + /// The name of the attribute. + public var name: String { + var length: UInt32 = 0 + let cstring = LLVMGetStringAttributeKind(llvm, &length) + return String.init(cString: cstring!) + } + + /// The value of the attribute. + public var value: String { + var length: UInt32 = 0 + let cstring = LLVMGetStringAttributeValue(llvm, &length) + return String.init(cString: cstring!) + } + + /// Retrieves the underlying LLVM attribute object. + public func asLLVM() -> LLVMAttributeRef { + return llvm + } +} + +extension Function { + /// Adds an enum attribute to the function, its return value or one of its + /// parameters. + /// + /// - parameter attrKind: The kind of the attribute to add. + /// - parameter value: The optional value of the attribute. + /// - parameter index: The index representing the function, its return value + /// or one of its parameters. + @discardableResult + public func addAttribute(_ attrKind: AttributeKind, value: UInt64 = 0, to index: AttributeIndex) -> EnumAttribute { + let ctx = LLVMGetModuleContext(LLVMGetGlobalParent(llvm)) + let attrRef = LLVMCreateEnumAttribute(ctx, attrKind.id, value) + LLVMAddAttributeAtIndex(llvm, index.rawValue, attrRef) + return EnumAttribute(llvm: attrRef!) + } + + /// Adds a string attribute to the function, its return value or one of its + /// parameters. + /// + /// - parameter name: The name of the attribute to add. + /// - parameter value: The optional value of the attribute. + /// - parameter index: The index representing the function, its return value + /// or one of its parameters. + @discardableResult + public func addAttribute(_ name: String, value: String = "", to index: AttributeIndex) -> StringAttribute { + let ctx = LLVMGetModuleContext(LLVMGetGlobalParent(llvm)) + let attrRef = name.withCString { cname -> LLVMAttributeRef! in + return value.withCString { cvalue in + return LLVMCreateStringAttribute(ctx, cname, UInt32(name.count), cvalue, UInt32(value.count)) + } + } + LLVMAddAttributeAtIndex(llvm, index.rawValue, attrRef) + return StringAttribute(llvm: attrRef!) + } + + /// Removes an attribute from the function, its return value or one of its + /// parameters. + /// + /// - parameter attr: The attribute to remove. + /// - parameter index: The index representing the function, its return value + /// or one of its parameters. + public func removeAttribute(_ attr: Attribute, from index: AttributeIndex) { + switch attr { + case let enumAttr as EnumAttribute: + LLVMRemoveEnumAttributeAtIndex(llvm, index.rawValue, enumAttr.kindID) + case let stringAttr as StringAttribute: + var length: UInt32 = 0 + let cstring = LLVMGetStringAttributeKind(stringAttr.llvm, &length) + LLVMRemoveStringAttributeAtIndex(llvm, index.rawValue, cstring, length) + default: + fatalError("unexpected attribute type") + } + } + + /// Removes an enum attribute from the function, its return value or one of + /// its parameters. + /// + /// - parameter attr: The kind of the attribute to remove. + /// - parameter index: The index representing the function, its return value + /// or one of its parameters. + public func removeAttribute(_ attrKind: AttributeKind, from index: AttributeIndex) { + // FIXME: Remove when LLVM 7.0.0 is released. + guard attrKind != .align + && attrKind != .alignstack + && attrKind != .allocsize + && attrKind != .dereferenceable + && attrKind != .dereferenceableOrNull else { + fatalError( + "Removing valued enum attributes crashes in LLVM <7.0.0 " + + "and is currently disabled in LLVMSwift.") + } + + LLVMRemoveEnumAttributeAtIndex(llvm, index.rawValue, attrKind.id) + } + + /// Removes a string attribute from the function, its return value or one of + /// its parameters. + /// + /// - parameter name: The name of the attribute to remove. + /// - parameter index: The index representing the function, its return value + /// or one of its parameters. + public func removeAttribute(_ name: String, from index: AttributeIndex) { + name.withCString { + LLVMRemoveStringAttributeAtIndex(llvm, index.rawValue, $0, UInt32(name.count)) + } + } + + /// Gets the attributes of the function, its return value or its parameters. + public func attributes(at index: AttributeIndex) -> [Attribute] { + let attrCount = LLVMGetAttributeCountAtIndex(llvm, index.rawValue) + var attrRefs: [LLVMAttributeRef?] = Array(repeating: nil, count: Int(attrCount)) + LLVMGetAttributesAtIndex(llvm, index.rawValue, &attrRefs) + return attrRefs.map { attrRef -> Attribute in + if LLVMIsEnumAttribute(attrRef) != 0 { + return EnumAttribute(llvm: attrRef!) + } else { + return StringAttribute(llvm: attrRef!) + } + } + } +} diff --git a/Tests/LLVMTests/IRAttributesSpec.swift b/Tests/LLVMTests/IRAttributesSpec.swift new file mode 100644 index 00000000..4aa79f55 --- /dev/null +++ b/Tests/LLVMTests/IRAttributesSpec.swift @@ -0,0 +1,264 @@ +import LLVM +import XCTest +import FileCheck +import Foundation + +class IRAttributesSpec : XCTestCase { + func testIRAttributes() { + XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["FNATTR"]) { + // FNATTR: ; ModuleID = '[[ModuleName:IRBuilderTest]]' + // FNATTR-NEXT: source_filename = "[[ModuleName]]" + let module = Module(name: "IRBuilderTest") + let builder = IRBuilder(module: module) + let fn = builder.addFunction("fn", + type: FunctionType(argTypes: [IntType.int32, IntType.int32], + returnType: IntType.int32)) + + // FNATTR: define i32 @fn(i32, i32) #0 { + fn.addAttribute(.nounwind, to: .function) + + // FNATTR-NEXT: entry: + let entry = fn.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: entry) + // FNATTR-NEXT: ret i32 0 + builder.buildRet(IntType.int32.constant(0)) + // FNATTR-NEXT: } + // FNATTR: attributes #0 = { nounwind } + module.dump() + }) + + XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["RVATTR"]) { + // RVATTR: ; ModuleID = '[[ModuleName:IRBuilderTest]]' + // RVATTR-NEXT: source_filename = "[[ModuleName]]" + let module = Module(name: "IRBuilderTest") + let builder = IRBuilder(module: module) + let fn = builder.addFunction("fn", + type: FunctionType(argTypes: [IntType.int32, IntType.int32], + returnType: IntType.int32)) + + // RVATTR: define signext i32 @fn(i32, i32) { + fn.addAttribute(.signext, to: .returnValue) + + // RVATTR-NEXT: entry: + let entry = fn.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: entry) + // RVATTR-NEXT: ret i32 0 + builder.buildRet(IntType.int32.constant(0)) + // RVATTR-NEXT: } + module.dump() + }) + + XCTAssert(fileCheckOutput(of: .stderr, withPrefixes: ["ARGATTR"]) { + // ARGATTR: ; ModuleID = '[[ModuleName:IRBuilderTest]]' + // ARGATTR-NEXT: source_filename = "[[ModuleName]]" + let module = Module(name: "IRBuilderTest") + let builder = IRBuilder(module: module) + let i8ptr = PointerType(pointee: IntType.int8) + let fn = builder.addFunction("fn", + type: FunctionType(argTypes: [IntType.int32, i8ptr], + returnType: IntType.int32)) + + // ARGATTR: define i32 @fn(i32 zeroext, i8* align 8) { + fn.addAttribute(.zeroext, to: .argument(0)) + fn.addAttribute(.align, value: 8, to: .argument(1)) + + // ARGATTR-NEXT: entry: + let entry = fn.appendBasicBlock(named: "entry") + builder.positionAtEnd(of: entry) + // ARGATTR-NEXT: ret i32 0 + builder.buildRet(IntType.int32.constant(0)) + // ARGATTR-NEXT: } + module.dump() + }) + } + + func testSetUnsetFunctionAttributes() { + let module = Module(name: "FNATTR") + let builder = IRBuilder(module: module) + + let i8ptr = PointerType(pointee: IntType.int8) + let fn = builder.addFunction("fn", + type: FunctionType(argTypes: [i8ptr], returnType: i8ptr)) + + // MARK: Enum attributes + + var enumAttr: EnumAttribute + let fnAttrs: [AttributeKind: UInt64] = [ + .alignstack: 8, + .allocsize: 8, + .alwaysinline: 0, + .builtin: 0, + .cold: 0, + .convergent: 0, + .inaccessiblememonly: 0, + .inaccessiblememOrArgmemonly: 0, + .inlinehint: 0, + .jumptable: 0, + .minsize: 0, + .naked: 0, + .noJumpTables: 0, + .nobuiltin: 0, + .noduplicate: 0, + .noimplicitfloat: 0, + .noinline: 0, + .nonlazybind: 0, + .noredzone: 0, + .noreturn: 0, + .norecurse: 0, + .nounwind: 0, + .optnone: 0, + .optsize: 0, + .readnone: 0, + .readonly: 0, + .writeonly: 0, + .argmemonly: 0, + .returnsTwice: 0, + .safestack: 0, + .sanitizeAddress: 0, + .sanitizeMemory: 0, + .sanitizeThread: 0, + .sanitizeHWAddress: 0, + .speculatable: 0, + .ssp: 0, + .sspreq: 0, + .sspstrong: 0, + .strictfp: 0, + .uwtable: 0, + ] + + for (attrKind, value) in fnAttrs { + enumAttr = fn.addAttribute(attrKind, value: value, to: .function) + XCTAssertEqual(enumAttr.value, value) + XCTAssert(fn.attributes(at: .function).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + // FIXME: Remove when LLVM 7.0.0 is released. + guard value == 0 else { continue } + + fn.removeAttribute(enumAttr, from: .function) + XCTAssertFalse(fn.attributes(at: .function).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + enumAttr = fn.addAttribute(attrKind, value: value, to: .function) + fn.removeAttribute(attrKind, from: .function) + XCTAssertFalse(fn.attributes(at: .function).contains { $0.asLLVM() == enumAttr.asLLVM() }) + } + + // MARK: String attributes + + var stringAttr: StringAttribute + for (name, value) in [("foo", ""), ("foo", "bar")] { + stringAttr = fn.addAttribute(name, value: value, to: .function) + XCTAssertEqual(stringAttr.name, name) + XCTAssertEqual(stringAttr.value, value) + XCTAssert(fn.attributes(at: .function).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + fn.removeAttribute(stringAttr, from: .function) + XCTAssertFalse(fn.attributes(at: .function).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + stringAttr = fn.addAttribute(name, to: .function) + fn.removeAttribute(name, from: .function) + XCTAssertFalse(fn.attributes(at: .function).contains { $0.asLLVM() == stringAttr.asLLVM() }) + } + } + + func testSetUnsetArgumentAttributes() { + let module = Module(name: "ARGATTR") + let builder = IRBuilder(module: module) + + let i8ptr = PointerType(pointee: IntType.int8) + let fn = builder.addFunction("fn", + type: FunctionType(argTypes: [i8ptr], returnType: i8ptr)) + + // MARK: Enum attributes + + var enumAttr: EnumAttribute + let argAttrs: [AttributeKind: UInt64] = [ + .zeroext: 0, + .signext: 0, + .inreg: 0, + .byval: 0, + .inalloca: 0, + .sret: 0, + .align: 8, + .noalias: 0, + .nocapture: 0, + .nest: 0, + .returned: 0, + .nonnull: 0, + .dereferenceable: 8, + .dereferenceableOrNull: 8, + .swiftself: 0, + .swifterror: 0, + ] + + for (attrKind, value) in argAttrs { + enumAttr = fn.addAttribute(attrKind, value: value, to: .returnValue) + XCTAssertEqual(enumAttr.value, value) + XCTAssert(fn.attributes(at: .returnValue).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + // FIXME: Remove when LLVM 7.0.0 is released. + guard value == 0 else { continue } + + fn.removeAttribute(enumAttr, from: .returnValue) + XCTAssertFalse(fn.attributes(at: .returnValue).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + enumAttr = fn.addAttribute(attrKind, value: value, to: .returnValue) + fn.removeAttribute(attrKind, from: .returnValue) + XCTAssertFalse(fn.attributes(at: .returnValue).contains { $0.asLLVM() == enumAttr.asLLVM() }) + } + + for (attrKind, value) in argAttrs { + enumAttr = fn.addAttribute(attrKind, value: value, to: .argument(0)) + XCTAssertEqual(enumAttr.value, value) + XCTAssert(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + // FIXME: Remove when LLVM 7.0.0 is released. + guard value == 0 else { continue } + + fn.removeAttribute(enumAttr, from: .argument(0)) + XCTAssertFalse(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == enumAttr.asLLVM() }) + + enumAttr = fn.addAttribute(attrKind, value: value, to: .argument(0)) + fn.removeAttribute(attrKind, from: .argument(0)) + XCTAssertFalse(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == enumAttr.asLLVM() }) + } + + // MARK: String attributes + + var stringAttr: StringAttribute + for (name, value) in [("foo", ""), ("foo", "bar")] { + stringAttr = fn.addAttribute(name, value: value, to: .returnValue) + XCTAssertEqual(stringAttr.name, name) + XCTAssertEqual(stringAttr.value, value) + XCTAssert(fn.attributes(at: .returnValue).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + fn.removeAttribute(stringAttr, from: .returnValue) + XCTAssertFalse(fn.attributes(at: .returnValue).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + stringAttr = fn.addAttribute(name, to: .returnValue) + fn.removeAttribute(name, from: .returnValue) + XCTAssertFalse(fn.attributes(at: .returnValue).contains { $0.asLLVM() == stringAttr.asLLVM() }) + } + + for (name, value) in [("foo", ""), ("foo", "bar")] { + stringAttr = fn.addAttribute(name, value: value, to: .argument(0)) + XCTAssertEqual(stringAttr.name, name) + XCTAssertEqual(stringAttr.value, value) + XCTAssert(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + fn.removeAttribute(stringAttr, from: .argument(0)) + XCTAssertFalse(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == stringAttr.asLLVM() }) + + stringAttr = fn.addAttribute(name, to: .argument(0)) + fn.removeAttribute(name, from: .argument(0)) + XCTAssertFalse(fn.attributes(at: .argument(0)).contains { $0.asLLVM() == stringAttr.asLLVM() }) + } + } + + #if !os(macOS) + static var allTests = testCase([ + ("testIRAttributes", testIRAttributes), + ("testSetUnsetFunctionAttributes", testSetUnsetFunctionAttributes), + ("testSetUnsetArgumentAttributes", testSetUnsetArgumentAttributes), + ]) + #endif +}