diff --git a/Compute.xctestplan b/Compute.xctestplan new file mode 100644 index 0000000..e85c203 --- /dev/null +++ b/Compute.xctestplan @@ -0,0 +1,68 @@ +{ + "configurations" : [ + { + "id" : "EF776845-F06A-498A-A7EE-EA72E31616F9", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "environmentVariableEntries" : [ + { + "key" : "AG_PRINT_LAYOUTS", + "value" : "1" + }, + { + "key" : "AG_PREFETCH_LAYOUTS", + "value" : "1" + }, + { + "key" : "AG_ASYNC_LAYOUTS", + "value" : "0" + } + ] + }, + "testTargets" : [ + { + "skippedTests" : { + "suites" : [ + { + "name" : "MetadataTests", + "testFunctions" : [ + "description(of:equals:)" + ] + } + ] + }, + "target" : { + "containerPath" : "container:", + "identifier" : "ComputeTests", + "name" : "ComputeTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "UtilitiesTests", + "name" : "UtilitiesTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "ComputeCxxTests", + "name" : "ComputeCxxTests" + } + }, + { + "target" : { + "containerPath" : "container:", + "identifier" : "ComputeCompatibilityTests", + "name" : "ComputeCompatibilityTests" + } + } + ], + "version" : 1 +} diff --git a/Package.swift b/Package.swift index 9b76c64..b59acfd 100644 --- a/Package.swift +++ b/Package.swift @@ -90,6 +90,24 @@ let package = Package( cxxSettings: [.headerSearchPath("")] ), .target(name: "ComputeCxxSwiftSupport"), + .testTarget( + name: "ComputeCxxTests", + dependencies: ["ComputeCxx"], + cSettings: [ + .headerSearchPath("../../Sources/ComputeCxx"), + .unsafeFlags([ + "-isystem", "\(swiftProjectPath)/swift/include", + "-isystem", "\(swiftProjectPath)/swift/stdlib/public/SwiftShims", + "-isystem", "\(swiftProjectPath)/swift/stdlib/public/runtime", + "-isystem", "\(swiftProjectPath)/llvm-project/llvm/include", + "-isystem", "\(swiftProjectPath)/llvm-project/clang/include", + "-isystem", "\(swiftProjectPath)/build/Default/swift/include", + "-isystem", "\(swiftProjectPath)/build/Default/llvm/include", + "-isystem", "\(swiftProjectPath)/build/Default/llvm/tools/clang/include", + ]), + ], + linkerSettings: [.linkedLibrary("swiftDemangle")] + ), ], cxxLanguageStandard: .cxx20 ) diff --git a/Sources/Compute/Attribute/AnyAttribute.swift b/Sources/Compute/Attribute/AnyAttribute.swift index 3796125..0e5f529 100644 --- a/Sources/Compute/Attribute/AnyAttribute.swift +++ b/Sources/Compute/Attribute/AnyAttribute.swift @@ -1,96 +1,84 @@ import ComputeCxx -public struct InputOptions {} - -public struct SearchOptions {} - -public struct AttributeFlags {} - extension AnyAttribute { public static var current: AnyAttribute? { - fatalError("not implemented") + let attribute = __AGGraphGetCurrentAttribute() + return attribute == .nil ? nil : attribute } public init(_ attribute: Attribute) { - fatalError("not implemented") + self = attribute.identifier } public func unsafeOffset(at offset: Int) -> AnyAttribute { - fatalError("not implemented") + // TODO: swift Int instead of UInt32 + return __AGGraphCreateOffsetAttribute(self, UInt32(offset)) } public func unsafeCast(to type: Value.Type) -> Attribute { - fatalError("not implemented") - } - - public var _bodyType: Any.Type { - fatalError("not implemented") - } - - public var _bodyPointer: UnsafeRawPointer { - fatalError("not implemented") + return Attribute(identifier: self) } public func visitBody(_ visitor: inout Visitor) { - fatalError("not implemented") + let type = info.type.pointee.typeID.type as! _AttributeBody.Type + type._visitSelf(info.body, visitor: &visitor) } public func mutateBody(as type: Body.Type, invalidating: Bool, _ mutator: (inout Body) -> Void) { - fatalError("not implemented") - } - - public func breadthFirstSearch(options: SearchOptions, _ predicate: (AnyAttribute) -> Bool) -> Bool { - fatalError("not implemented") + Graph.mutateAttribute(self, type: Metadata(type), invalidating: invalidating) { pointer in + mutator(&pointer.assumingMemoryBound(to: Body.self).pointee) + } } - public var valueType: Any.Type { - fatalError("not implemented") + public func breadthFirstSearch(options: AGSearchOptions, _ predicate: (AnyAttribute) -> Bool) -> Bool { + return Graph.search(attribute: self, options: options, predicate: predicate) } - public func setFlags(_ newFlags: AttributeFlags, mask: AttributeFlags) { - fatalError("not implemented") + public func setFlags(_ newFlags: AGAttributeFlags, mask: AGAttributeFlags) { + flags = flags.subtracting(mask).union(newFlags.intersection(mask)) } - public func addInput(_ input: AnyAttribute, options: InputOptions, token: Int) { - fatalError("not implemented") + public func addInput(_ input: AnyAttribute, options: AGInputOptions, token: Int) { + __AGGraphAddInput(self, input, options) } - public func addInput(_ input: Attribute, options: InputOptions, token: Int) { - fatalError("not implemented") + public func addInput(_ input: Attribute, options: AGInputOptions, token: Int) { + addInput(input.identifier, options: options, token: token) } public var indirectDependency: AnyAttribute? { get { - fatalError("not implemented") + let indirectDependency = __AGGraphGetIndirectDependency(self) + return indirectDependency == .nil ? nil : indirectDependency } set { - fatalError("not implemented") + __AGGraphSetIndirectDependency(self, newValue ?? .nil) } } -} + public var _bodyType: Any.Type { + return info.type.pointee.typeID.type + } -extension AnyAttribute: @retroactive CustomStringConvertible { + public var _bodyPointer: UnsafeRawPointer { + return info.body + } - public var description: String { - fatalError("not implemented") + public var valueType: Any.Type { + return info.type.pointee.valueTypeID.type } } -extension AnyAttribute: @retroactive Equatable { +extension AnyAttribute: @retroactive CustomStringConvertible { - public static func == (_ lhs: AnyAttribute, _ rhs: AnyAttribute) -> Bool { - fatalError("not implemented") + public var description: String { + return "#\(rawValue)" } } -extension AnyAttribute: @retroactive Hashable { +extension AnyAttribute: @retroactive Equatable {} - public func hash(into hasher: inout Hasher) { - fatalError("not implemented") - } - -} +extension AnyAttribute: @retroactive Hashable {} diff --git a/Sources/Compute/Attribute/Attribute.swift b/Sources/Compute/Attribute/Attribute.swift index 3305b60..220b7f9 100644 --- a/Sources/Compute/Attribute/Attribute.swift +++ b/Sources/Compute/Attribute/Attribute.swift @@ -1,11 +1,5 @@ import ComputeCxx -public struct ValueState {} - -public struct ValueOptions {} - -public struct ChangedValueFlags {} - @propertyWrapper @dynamicMemberLookup public struct Attribute { @@ -17,150 +11,209 @@ public struct Attribute { } public init(_ attribute: Attribute) { - fatalError("not implemented") + self = attribute } - public init(type: Value.Type) { - fatalError("not implemented") + public init(value: Value) { + self = withUnsafePointer(to: value) { valuePointer in + return withUnsafePointer(to: External()) { bodyPointer in + return Attribute(body: bodyPointer, value: valuePointer, flags: .option16) { + return External._update + } + } + } } - public init(value: Value) { - fatalError("not implemented") + public init(type: Value.Type) { + self = withUnsafePointer(to: External()) { bodyPointer in + // TODO: what are flags + return Attribute(body: bodyPointer, value: nil, flags: .option16) { + return External._update + } + } } public init( - body: UnsafePointer, value: UnsafePointer?, flags: AttributeTypeFlags, + body: UnsafePointer, + value: UnsafePointer?, + flags: AGAttributeTypeFlags, update: () -> (UnsafeMutableRawPointer, AnyAttribute) -> Void ) { - fatalError("not implemented") - } - - public func unsafeCast(to type: T.Type) -> Attribute { - fatalError("not implemented") - } + guard let graphContext = Subgraph.currentGraphContext else { + preconditionFailure("attempting to create attribute with no subgraph: \(Body.self)") + } - public func unsafeOffset(at offset: Int, as type: T) -> Attribute { - fatalError("not implemented") + let typeID = graphContext.internAttributeType( + type: Metadata(Body.self) + ) { + let attributeType = AGAttributeType( + selfType: Body.self, + bodyType: Body.self, + valueType: Value.self, + flags: flags, + update: update() + ) + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: attributeType) + return UnsafeRawPointer(pointer) + } + identifier = __AGGraphCreateAttribute(typeID, body, value) } public func applying(offset: PointerOffset) -> Attribute { - fatalError("not implemented") + return unsafeOffset(at: offset.byteOffset, as: Member.self) } - public func breadthFirstSearch(options: SearchOptions, _ predicate: (AnyAttribute) -> Bool) -> Bool { - fatalError("not implemented") + public func breadthFirstSearch(options: AGSearchOptions, _ predicate: (AnyAttribute) -> Bool) -> Bool { + return identifier.breadthFirstSearch(options: options, predicate) } public func visitBody(_ visitor: inout Visitor) { - fatalError("not implemented") + identifier.visitBody(&visitor) } public func mutateBody(as bodyType: Body.Type, invalidating: Bool, _ mutator: (inout Body) -> Void) { - fatalError("not implemented") + identifier.mutateBody(as: bodyType, invalidating: invalidating, mutator) } public func validate() { - fatalError("not implemented") + __AGGraphVerifyType(identifier, Metadata(Value.self)) } public var value: Value { unsafeAddress { - fatalError("not implemented") + return __AGGraphGetValue(identifier, [], Metadata(Value.self)) + .value + .assumingMemoryBound(to: Value.self) } - set { - fatalError("not implemented") + nonmutating set { + _ = setValue(newValue) } } public func setValue(_ value: Value) -> Bool { - fatalError("not implemented") + return withUnsafePointer(to: value) { valuePointer in + return __AGGraphSetValue(identifier, valuePointer, Metadata(Value.self)) + } } public var hasValue: Bool { - fatalError("not implemented") + return __AGGraphHasValue(identifier) } - public var valueState: ValueState { - fatalError("not implemented") + public var valueState: AGValueState { + return __AGGraphGetValueState(identifier) } public func prefetchValue() { - fatalError("not implemented") + __AGGraphPrefetchValue(identifier) } public func updateValue() { - fatalError("not implemented") + __AGGraphUpdateValue(identifier, 0) } public func invalidateValue() { - fatalError("not implemented") + __AGGraphInvalidateValue(identifier) } - public func changedValue(options: ValueOptions) -> (value: Value, changed: Bool) { - fatalError("not implemented") + public func changedValue(options: AGValueOptions) -> (value: Value, changed: Bool) { + let value = __AGGraphGetValue(identifier, options, Metadata(Value.self)) + return ( + value.value.assumingMemoryBound(to: Value.self).pointee, + value.changed + ) } - public var flags: AttributeFlags { + public var flags: AGAttributeFlags { get { - fatalError("not implemented") + return identifier.flags } - set { - fatalError("not implemented") + nonmutating set { + identifier.flags = newValue } } - public func setFlags(_ flags: AttributeFlags, mask: AttributeFlags) { - fatalError("not implemented") + public func setFlags(_ newFlags: AGAttributeFlags, mask: AGAttributeFlags) { + identifier.setFlags(newFlags, mask: mask) } - public func valueAndFlags(options: ValueOptions) -> (value: Value, flags: ChangedValueFlags) { - fatalError("not implemented") + public func valueAndFlags(options: AGValueOptions) -> (value: Value, flags: AGChangedValueFlags) { + let value = __AGGraphGetValue(identifier, options, Metadata(Value.self)) + return ( + value.value.assumingMemoryBound(to: Value.self).pointee, + value.changed ? .changed : [] + ) } - public func addInput(_ input: Attribute, options: InputOptions, token: Int) { - fatalError("not implemented") + public func addInput(_ input: Attribute, options: AGInputOptions, token: Int) { + identifier.addInput(input, options: options, token: token) } - public func addInput(_ input: AnyAttribute, options: InputOptions, token: Int) { - fatalError("not implemented") + public func addInput(_ input: AnyAttribute, options: AGInputOptions, token: Int) { + identifier.addInput(input, options: options, token: token) } public var graph: Graph { - fatalError("not implemented") + // TODO: retain or not? + return __AGGraphGetAttributeGraph(identifier).takeUnretainedValue() } public var subgraph: Subgraph { - fatalError("not implemented") + // TODO: retain or not? + return __AGGraphGetAttributeSubgraph(identifier).takeUnretainedValue() } public var subgraphOrNil: Subgraph? { - fatalError("not implemented") + // TODO: retain or not? + return __AGGraphGetAttributeSubgraph(identifier).takeUnretainedValue() } public var wrappedValue: Value { unsafeAddress { - fatalError("not implemented") + return __AGGraphGetValue(identifier, [], Metadata(Value.self)) + .value + .assumingMemoryBound(to: Value.self) } - set { - fatalError("not implemented") + nonmutating set { + _ = setValue(newValue) } } public var projectedValue: Attribute { get { - fatalError("not implemented") + return self } set { - fatalError("not implemented") + self = newValue } } - public subscript(offset: (inout Value) -> PointerOffset) -> Attribute { - fatalError("not implemented") + public subscript(offset body: (inout Value) -> PointerOffset) -> Attribute { + unsafeOffset(at: PointerOffset.offset(body).byteOffset, as: Member.self) + } + + public subscript(keyPath keyPath: KeyPath) -> Attribute { + if let offset = MemoryLayout.offset(of: keyPath) { + return unsafeOffset(at: offset, as: Member.self) + } else { + return Attribute(Focus(root: self, keyPath: keyPath)) + } } public subscript(dynamicMember keyPath: KeyPath) -> Attribute { - fatalError("not implemented") + self[keyPath: keyPath] + } + + public func unsafeCast(to type: T.Type) -> Attribute { + return identifier.unsafeCast(to: type) + } + + public func unsafeOffset(at offset: Int, as _: Member.Type) -> Attribute { + // TODO: fix Int/UInt32 + return Attribute( + identifier: __AGGraphCreateOffsetAttribute2(identifier, UInt32(offset), MemoryLayout.size) + ) } } @@ -168,7 +221,7 @@ public struct Attribute { extension Attribute: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return identifier.description } } @@ -191,12 +244,42 @@ extension Attribute: Hashable { extension Attribute { - public init(_ body: Body, initialValue: Value? = nil) where Body.Value == Value { - fatalError("not implemented") + public init(_ body: Body) where Body.Value == Value { + self = withUnsafePointer(to: body) { bodyPointer in + return Attribute(body: bodyPointer, value: nil, flags: []) { + return Body._update + } + } + + } + + public init(_ body: Body, initialValue: Body.Value) where Body.Value == Value { + self = withUnsafePointer(to: body) { bodyPointer in + return withUnsafePointer(to: initialValue) { initialValuePointer in + return Attribute(body: bodyPointer, value: initialValuePointer, flags: []) { + return Body._update + } + } + } + } - public init(_ body: Body, initialValue: Value? = nil) where Body.Value == Value { - fatalError("not implemented") + public init(_ body: Body) where Body.Value == Value { + self = withUnsafePointer(to: body) { bodyPointer in + return Attribute(body: bodyPointer, value: nil, flags: []) { + return Body._update + } + } + } + + public init(_ body: Body, initialValue: Body.Value) where Body.Value == Value { + self = withUnsafePointer(to: body) { bodyPointer in + return withUnsafePointer(to: initialValue) { initialValuePointer in + return Attribute(body: bodyPointer, value: initialValuePointer, flags: []) { + return Body._update + } + } + } } } diff --git a/Sources/Compute/Attribute/AttributeType.swift b/Sources/Compute/Attribute/AttributeType.swift index c6fe2f5..9d09741 100644 --- a/Sources/Compute/Attribute/AttributeType.swift +++ b/Sources/Compute/Attribute/AttributeType.swift @@ -1,3 +1,78 @@ -public struct AttributeTypeFlags { +import ComputeCxx +extension Graph { + + @_extern(c, "AGGraphInternAttributeType") + fileprivate static func internAttributeType( + _ graph: UnsafeRawPointer, + type: Metadata, + makeAttributeType: () -> UnsafeRawPointer + ) + -> UInt32 + +} + +extension AGUnownedGraphRef { + + @inline(__always) + func internAttributeType(type: Metadata, makeAttributeType: () -> UnsafeRawPointer) -> UInt32 { + return Graph.internAttributeType( + unsafeBitCast(self, to: UnsafeRawPointer.self), + type: type, + makeAttributeType: makeAttributeType + ) + } + +} + +extension AGAttributeType { + + // FUN_1afe864c4 + static func allocate( + selfType: Any.Type, + bodyType: _AttributeBody.Type, + valueType: Any.Type, + flags: AGAttributeTypeFlags, + update: () -> (UnsafeMutableRawPointer, AnyAttribute) -> Void + ) -> UnsafeMutablePointer { + + let attributeType = AGAttributeType( + selfType: selfType, + bodyType: bodyType, + valueType: valueType, + flags: flags, + update: update() + ) + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: attributeType) + return pointer + + } + + // sub_1AFE86960 + init( + selfType: Any.Type, + bodyType: _AttributeBody.Type, // witness table + valueType: Any.Type, + flags: AGAttributeTypeFlags, + update: @escaping (UnsafeMutableRawPointer, AnyAttribute) -> Void, + ) { + self.init() + + var effectiveFlags = flags + effectiveFlags.insert(bodyType.flags) + effectiveFlags.insert(AGAttributeTypeFlags(rawValue: UInt32(bodyType.comparisonMode.rawValue))) + if bodyType._hasDestroySelf && !effectiveFlags.contains(.option4) { + effectiveFlags.insert(.option4) // TODO: is this flag reallyHasDestroySelf?? + } + + self.typeID = Metadata(selfType) + self.valueTypeID = Metadata(valueType) + self.update_function = unsafeBitCast(update, to: AGClosureStorage2.self) + self.callbacks = nil + self.flags = effectiveFlags + + self.initial_body_type_id = Metadata(selfType) + self.initial_body_witness_table = Metadata(bodyType) // not sure if Metadata works for witness tables + } } diff --git a/Sources/Compute/Attribute/Body/AttributeBody.swift b/Sources/Compute/Attribute/Body/AttributeBody.swift index 988618a..2df992e 100644 --- a/Sources/Compute/Attribute/Body/AttributeBody.swift +++ b/Sources/Compute/Attribute/Body/AttributeBody.swift @@ -3,34 +3,35 @@ import ComputeCxx public protocol _AttributeBody { static func _destroySelf(_ self: UnsafeMutableRawPointer) + static var _hasDestroySelf: Bool { get } + static func _updateDefault(_ default: UnsafeMutableRawPointer) static var comparisonMode: AGComparisonMode { get } - static var _hasDestroySelf: Bool { get } - static var flags: AttributeTypeFlags { get } + static var flags: AGAttributeTypeFlags { get } } extension _AttributeBody { public static func _destroySelf(_ self: UnsafeMutableRawPointer) { - fatalError("not implemented") + } - public static func _updateDefault(_ default: UnsafeMutableRawPointer) { - fatalError("not implemented") + public static var _hasDestroySelf: Bool { + return false } - public static var comparisonMode: AGComparisonMode { - fatalError("not implemented") + public static func _updateDefault(_ default: UnsafeMutableRawPointer) { + } - public static var _hasDestroySelf: Bool { - fatalError("not implemented") + public static var comparisonMode: AGComparisonMode { + return .equatableUnlessPOD } - public static var flags: AttributeTypeFlags { - fatalError("not implemented") + public static var flags: AGAttributeTypeFlags { + return .option8 } } @@ -38,7 +39,7 @@ extension _AttributeBody { extension _AttributeBody { public var updateWasCancelled: Bool { - fatalError("not implemented") + return __AGGraphUpdateWasCancelled() } } diff --git a/Sources/Compute/Attribute/Body/AttributeBodyVisitor.swift b/Sources/Compute/Attribute/Body/AttributeBodyVisitor.swift index 37a9437..7f19492 100644 --- a/Sources/Compute/Attribute/Body/AttributeBodyVisitor.swift +++ b/Sources/Compute/Attribute/Body/AttributeBodyVisitor.swift @@ -3,3 +3,11 @@ public protocol AttributeBodyVisitor { func visit(body: UnsafePointer) } + +extension _AttributeBody { + + static func _visitSelf(_ self: UnsafeRawPointer, visitor: inout Visitor) { + visitor.visit(body: self.assumingMemoryBound(to: Self.self)) + } + +} diff --git a/Sources/Compute/Attribute/External.swift b/Sources/Compute/Attribute/External.swift index b430151..4aee0dc 100644 --- a/Sources/Compute/Attribute/External.swift +++ b/Sources/Compute/Attribute/External.swift @@ -5,7 +5,7 @@ public struct External { public init() {} public static func _update(_: UnsafeMutableRawPointer, attribute: AnyAttribute) { - fatalError("not implemented") + } } @@ -13,11 +13,11 @@ public struct External { extension External: _AttributeBody { public static var comparisonMode: AGComparisonMode { - fatalError("not implemented") + return .equatableAlways } - public static var flags: AttributeTypeFlags { - fatalError("not implemented") + public static var flags: AGAttributeTypeFlags { + return [] } } @@ -25,7 +25,7 @@ extension External: _AttributeBody { extension External: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return Metadata(Value.self).description } } diff --git a/Sources/Compute/Attribute/Focus.swift b/Sources/Compute/Attribute/Focus.swift index df1a7f9..3b790f6 100644 --- a/Sources/Compute/Attribute/Focus.swift +++ b/Sources/Compute/Attribute/Focus.swift @@ -16,8 +16,8 @@ extension Focus: Rule { return root.value[keyPath: keyPath] } - public static var flags: AttributeTypeFlags { - fatalError("not implemented") + public static var flags: AGAttributeTypeFlags { + return [] } } @@ -25,7 +25,7 @@ extension Focus: Rule { extension Focus: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return "• \(Metadata(Value.self).description)" } } diff --git a/Sources/Compute/Attribute/Indirect/IndirectAttribute.swift b/Sources/Compute/Attribute/Indirect/IndirectAttribute.swift index f2dbfe0..35eec34 100644 --- a/Sources/Compute/Attribute/Indirect/IndirectAttribute.swift +++ b/Sources/Compute/Attribute/Indirect/IndirectAttribute.swift @@ -7,63 +7,72 @@ public struct IndirectAttribute { public var identifier: AnyAttribute public init(source: Attribute) { - fatalError("not implemented") - } - - public var attribute: Attribute { - fatalError("not implemented") + identifier = __AGGraphCreateIndirectAttribute2(source.identifier, MemoryLayout.size) } public var source: Attribute { get { - fatalError("not implemented") + return Attribute(identifier: __AGGraphGetIndirectAttribute(identifier)) } set { - fatalError("not implemented") + __AGGraphSetIndirectAttribute(identifier, newValue.identifier) } } public func resetSource() { - fatalError("not implemented") + __AGGraphResetIndirectAttribute(identifier, false) + } + + public var attribute: Attribute { + return Attribute(identifier: identifier) } public var dependency: AnyAttribute? { get { - fatalError("not implemented") + let result = __AGGraphGetIndirectDependency(identifier) + return result == .nil ? nil : result } set { - fatalError("not implemented") + __AGGraphSetIndirectDependency(identifier, newValue ?? .nil) } } - + public var value: Value { get { - fatalError("not implemented") + return Attribute(identifier: identifier).value } - set { - fatalError("not implemented") + nonmutating set { + Attribute(identifier: identifier).value = newValue + } + nonmutating _modify { + yield &Attribute(identifier: identifier).value } } - public func changedValue(options: ValueOptions) -> (value: Value, changed: Bool) { - fatalError("not implemented") + public func changedValue(options: AGValueOptions) -> (value: Value, changed: Bool) { + return Attribute(identifier: identifier).changedValue(options: options) } - + public var wrappedValue: Value { get { - fatalError("not implemented") + value } - set { - fatalError("not implemented") + nonmutating set { + value = newValue + } + nonmutating _modify { + yield &value } } + + public var projectedValue: Attribute { - fatalError("not implemented") + return Attribute(identifier: identifier) } public subscript(dynamicMember keyPath: KeyPath) -> Attribute { - fatalError("not implemented") + return Attribute(identifier: identifier)[dynamicMember: keyPath] } } diff --git a/Sources/Compute/Attribute/Map.swift b/Sources/Compute/Attribute/Map.swift index 1b3825c..57ddc62 100644 --- a/Sources/Compute/Attribute/Map.swift +++ b/Sources/Compute/Attribute/Map.swift @@ -16,8 +16,8 @@ extension Map: Rule { return body(arg.value) } - public static var flags: AttributeTypeFlags { - fatalError("not implemented") + public static var flags: AGAttributeTypeFlags { + return [] } } @@ -25,7 +25,7 @@ extension Map: Rule { extension Map: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return "• \(Metadata(Value.self).description)" } } diff --git a/Sources/Compute/Attribute/Observed/ObservedAttribute.swift b/Sources/Compute/Attribute/Observed/ObservedAttribute.swift index 67329a1..10b1fa5 100644 --- a/Sources/Compute/Attribute/Observed/ObservedAttribute.swift +++ b/Sources/Compute/Attribute/Observed/ObservedAttribute.swift @@ -6,11 +6,11 @@ public protocol ObservedAttribute: _AttributeBody { extension ObservedAttribute { public static func _destroySelf(_ self: UnsafeMutableRawPointer) { - fatalError("not implemented") + self.assumingMemoryBound(to: Self.self).pointee.destroy() } public static var _hasDestroySelf: Bool { - fatalError("not implemented") + return true } } diff --git a/Sources/Compute/Attribute/Optional/AnyOptionalAttribute.swift b/Sources/Compute/Attribute/Optional/AnyOptionalAttribute.swift index 2d9fa0c..846f15e 100644 --- a/Sources/Compute/Attribute/Optional/AnyOptionalAttribute.swift +++ b/Sources/Compute/Attribute/Optional/AnyOptionalAttribute.swift @@ -2,47 +2,51 @@ import ComputeCxx public struct AnyOptionalAttribute { - public static var current: AnyOptionalAttribute? { - fatalError("not implemented") + public static var current: AnyOptionalAttribute { + return AnyOptionalAttribute(__AGGraphGetCurrentAttribute()) } public var identifier: AnyAttribute public init() { - fatalError("not implemented") + identifier = .nil } - public init(_ weakAttribute: AnyWeakAttribute) { - fatalError("not implemented") + public init(_ attribute: AnyAttribute) { + identifier = attribute } public init(_ attribute: AnyAttribute?) { - fatalError("not implemented") + identifier = attribute ?? .nil } - - public init(_ attribute: AnyAttribute) { - fatalError("not implemented") + + public init(_ weakAttribute: AnyWeakAttribute) { + identifier = __AGWeakAttributeGetAttribute(weakAttribute) } public init(_ optionalAttribute: OptionalAttribute) { - fatalError("not implemented") - } - - public func unsafeCast(to _: Value.Type) -> OptionalAttribute { - fatalError("not implemented") + self = optionalAttribute.base } public var attribute: AnyAttribute? { get { - fatalError("not implemented") + return identifier == .nil ? nil : identifier } set { - fatalError("not implemented") + identifier = newValue ?? .nil } } public func map(_ transform: (AnyAttribute) -> T) -> T? { - fatalError("not implemented") + if let attribute = attribute { + return transform(attribute) + } else { + return nil + } + } + + public func unsafeCast(to _: Value.Type) -> OptionalAttribute { + return OptionalAttribute(base: self) } } @@ -50,7 +54,7 @@ public struct AnyOptionalAttribute { extension AnyOptionalAttribute: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return attribute?.description ?? "nil" } } diff --git a/Sources/Compute/Attribute/Optional/OptionalAttribute.swift b/Sources/Compute/Attribute/Optional/OptionalAttribute.swift index 893234a..7610e31 100644 --- a/Sources/Compute/Attribute/Optional/OptionalAttribute.swift +++ b/Sources/Compute/Attribute/Optional/OptionalAttribute.swift @@ -1,65 +1,72 @@ @propertyWrapper @dynamicMemberLookup public struct OptionalAttribute { - + public var base: AnyOptionalAttribute - - public init(base: AnyOptionalAttribute) { - self.base = base - } - + public init() { - fatalError("not implemented") + base = AnyOptionalAttribute() } - - public init(_ weakAttribute: WeakAttribute) { - fatalError("not implemented") + + public init(base: AnyOptionalAttribute) { + self.base = base } - + public init(_ attribute: Attribute) { - fatalError("not implemented") + base = AnyOptionalAttribute(attribute.identifier) } - + public init(_ attribute: Attribute?) { - fatalError("not implemented") + base = AnyOptionalAttribute(attribute?.identifier) } - + + public init(_ weakAttribute: WeakAttribute) { + base = AnyOptionalAttribute(weakAttribute.base) + } + public var attribute: Attribute? { get { - fatalError("not implemented") + return base.attribute?.unsafeCast(to: Value.self) } set { - fatalError("not implemented") + base.attribute = newValue?.identifier } } - + public var value: Value? { - fatalError("not implemented") + return attribute?.value } - public func changedValue() -> (value: Value, changed: Bool)? { - fatalError("not implemented") + public func changedValue(options: AGValueOptions) -> (value: Value, changed: Bool)? { + return attribute?.changedValue(options: options) } public func map(_ transform: (Attribute) -> T) -> T? { - fatalError("not implemented") + if let attribute = attribute { + return transform(attribute) + } else { + return nil + } } - + public var wrappedValue: Value? { - fatalError("not implemented") + return value } - + public var projectedValue: Attribute? { get { - fatalError("not implemented") + return attribute } set { - fatalError("not implemented") + attribute = newValue + } + _modify { + yield &attribute } } - + public subscript(dynamicMember keyPath: KeyPath) -> Attribute? { - fatalError("not implemented") + return attribute?[dynamicMember: keyPath] } } @@ -67,7 +74,7 @@ public struct OptionalAttribute { extension OptionalAttribute: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return attribute?.description ?? "nil" } } diff --git a/Sources/Compute/Attribute/PointerOffset.swift b/Sources/Compute/Attribute/PointerOffset.swift index b0265bd..99f633c 100644 --- a/Sources/Compute/Attribute/PointerOffset.swift +++ b/Sources/Compute/Attribute/PointerOffset.swift @@ -7,15 +7,21 @@ public struct PointerOffset { } public static func of(_ member: inout Member) -> PointerOffset { - fatalError("not implemented") + return withUnsafePointer(to: &member) { memberPointer in + let offset = UnsafeRawPointer(memberPointer) - UnsafeRawPointer(invalidScenePointer()) + return PointerOffset(byteOffset: offset) + } } public static func offset(_ body: (inout Base) -> PointerOffset) -> PointerOffset { - fatalError("not implemented") + guard MemoryLayout.size != 0 else { + return PointerOffset(byteOffset: 0) + } + return body(&invalidScenePointer().pointee) } public static func invalidScenePointer() -> UnsafeMutablePointer { - fatalError("not implemented") + return UnsafeMutablePointer(bitPattern: MemoryLayout.stride)! } } @@ -50,7 +56,7 @@ extension UnsafePointer { .assumingMemoryBound(to: Member.self) } - public subscript(offset: PointerOffset) -> Member { + public subscript(offset offset: PointerOffset) -> Member { unsafeAddress { return UnsafeRawPointer(self) .advanced(by: offset.byteOffset) @@ -77,7 +83,7 @@ extension UnsafeMutablePointer { .advanced(by: offset.byteOffset) .assumingMemoryBound(to: Member.self) } - unsafeMutableAddress { + nonmutating unsafeMutableAddress { return UnsafeMutableRawPointer(self) .advanced(by: offset.byteOffset) .assumingMemoryBound(to: Member.self) diff --git a/Sources/Compute/Attribute/Rule/AnyRuleContext.swift b/Sources/Compute/Attribute/Rule/AnyRuleContext.swift index 4b5b73d..afb8862 100644 --- a/Sources/Compute/Attribute/Rule/AnyRuleContext.swift +++ b/Sources/Compute/Attribute/Rule/AnyRuleContext.swift @@ -9,41 +9,55 @@ public struct AnyRuleContext { } public init(_ ruleContext: RuleContext) { - fatalError("not implemented") - } - - public func unsafeCast(to type: Value.Type) -> RuleContext { - fatalError("not implemented") + self.attribute = ruleContext.attribute.identifier } public func update(body: () -> Void) { - fatalError("not implemented") + Graph.withUpdate(attribute: attribute, body: body) } - public func changedValue(of attribute: Attribute, options: ValueOptions) -> ( + public func changedValue(of input: Attribute, options: AGValueOptions) -> ( value: Value, changed: Bool ) { - fatalError("not implemented") + let result = __AGGraphGetInputValue(attribute, input.identifier, options, Metadata(Value.self)) + return ( + result.value.assumingMemoryBound(to: Value.self).pointee, + result.changed + ) } - public func valueAndFlags(of attribute: Attribute, options: ValueOptions) -> ( - value: Value, flags: ChangedValueFlags + public func valueAndFlags(of input: Attribute, options: AGValueOptions) -> ( + value: Value, flags: AGChangedValueFlags ) { - fatalError("not implemented") + let result = __AGGraphGetInputValue(attribute, input.identifier, options, Metadata(Value.self)) + return ( + result.value.assumingMemoryBound(to: Value.self).pointee, + result.changed ? .changed : [] + ) } - public subscript(_ attribute: Attribute) -> Value { + public subscript(_ input: Attribute) -> Value { unsafeAddress { - fatalError("not implemented") + return __AGGraphGetInputValue(attribute, input.identifier, [], Metadata(Value.self)) + .value + .assumingMemoryBound(to: Value.self) + } + } + + public subscript(_ weakInput: WeakAttribute) -> Value? { + return weakInput.attribute.map { input in + return self[input] } } - public subscript(_ weakAttribute: WeakAttribute) -> Value? { - fatalError("not implemented") + public subscript(_ optionalInput: OptionalAttribute) -> Value? { + return optionalInput.attribute.map { input in + return self[input] + } } - public subscript(_ optionalAttribute: OptionalAttribute) -> Value? { - fatalError("not implemented") + public func unsafeCast(to type: Value.Type) -> RuleContext { + return RuleContext(attribute: Attribute(identifier: attribute)) } } diff --git a/Sources/Compute/Attribute/Rule/Rule.swift b/Sources/Compute/Attribute/Rule/Rule.swift index 066c475..0cbd228 100644 --- a/Sources/Compute/Attribute/Rule/Rule.swift +++ b/Sources/Compute/Attribute/Rule/Rule.swift @@ -16,12 +16,20 @@ extension Rule { } public static func _updateDefault(_ self: UnsafeMutableRawPointer) { - fatalError("not implemented") - + guard let initialValue = initialValue else { + return + } + withUnsafePointer(to: initialValue) { initialValuePointer in + __AGGraphSetOutputValue(initialValuePointer, Metadata(Value.self)) + } } - public static func _update(_ self: UnsafeMutableRawPointer, attribute: AnyAttribute) { - fatalError("not implemented") + public static func _update(_ pointer: UnsafeMutableRawPointer, attribute: AnyAttribute) { + let rule = pointer.assumingMemoryBound(to: Self.self) + let value = rule.pointee.value + withUnsafePointer(to: value) { valuePointer in + __AGGraphSetOutputValue(valuePointer, Metadata(Value.self)) + } } } @@ -29,36 +37,102 @@ extension Rule { extension Rule { public var bodyChanged: Bool { - fatalError("not implemented") + // guessing this is it + return __AGGraphCurrentAttributeWasModified() } public var attribute: Attribute { - fatalError("not implemented") + guard let attribute = AnyAttribute.current else { + preconditionFailure() + } + return Attribute(identifier: attribute) } public var context: RuleContext { - fatalError("not implemented") + return RuleContext(attribute: attribute) } } -public struct CachedValueOptions {} +extension Graph { + + @_extern(c, "AGGraphReadCachedAttribute") + static func readCachedAttribute( + identifier: UInt64, + type: Metadata, + body: UnsafeMutableRawPointer, + valueType: Metadata, + options: AGCachedValueOptions, + attribute: AnyAttribute, + changed: UnsafeMutablePointer?, + internAttributeType: (AGUnownedGraphRef) -> UInt32 + ) + -> UnsafeRawPointer +} -extension Rule where Value: Hashable { +extension Rule where Self: Hashable { - public func cachedValue(options: CachedValueOptions, owner: AnyAttribute?) -> Value { - fatalError("not implemented") + public func cachedValue(options: AGCachedValueOptions, owner: AnyAttribute?) -> Value { + return withUnsafePointer(to: self) { selfPointer in + let result = Self._cachedValue(options: options, owner: owner, hashValue: hashValue, bodyPtr: selfPointer) { + return Self._update(_:attribute:) + } + return result.pointee + } } - public func cachedValueIfExists(options: CachedValueOptions, owner: AnyAttribute?) -> Value? { - fatalError("not implemented") + public func cachedValueIfExists(options: AGCachedValueOptions, owner: AnyAttribute?) -> Value? { + let result = withUnsafePointer(to: self) { selfPointer in + return __AGGraphReadCachedAttributeIfExists( + UInt64(hashValue), // TODO: bitPattern? + Metadata(Self.self), + UnsafeMutablePointer(mutating: selfPointer), // TODO: need to mutate this? make const at C and propogate... + Metadata(Value.self), + options, + owner ?? .nil, + nil + ) + } + guard let result = result else { + return nil + } + return result.assumingMemoryBound(to: Value.self).pointee } public static func _cachedValue( - options: CachedValueOptions, owner: AnyAttribute?, hashValue: Int, bodyPtr: UnsafeRawPointer, + options: AGCachedValueOptions, + owner: AnyAttribute?, + hashValue: Int, + bodyPtr: UnsafeRawPointer, update: () -> (UnsafeMutableRawPointer, AnyAttribute) -> Void ) -> UnsafePointer { - fatalError("not implemented") + return withUnsafePointer(to: self) { selfPointer in + let result = Graph.readCachedAttribute( + identifier: UInt64(hashValue), + type: Metadata(Self.self), + body: UnsafeMutablePointer(mutating: selfPointer), + valueType: Metadata(Value.self), + options: options, + attribute: owner ?? .nil, + changed: nil + ) { graphContext in + return graphContext.internAttributeType( + type: Metadata(Self.self) + ) { + let attributeType = AGAttributeType( + selfType: Self.self, + bodyType: Self.self, + valueType: Value.self, + flags: [], // TODO: check flags are empty + update: update() + ) + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: attributeType) + return UnsafeRawPointer(pointer) + } + } + return result.assumingMemoryBound(to: Value.self) + } } } diff --git a/Sources/Compute/Attribute/Rule/RuleContext.swift b/Sources/Compute/Attribute/Rule/RuleContext.swift index bf815ea..f4e12fe 100644 --- a/Sources/Compute/Attribute/Rule/RuleContext.swift +++ b/Sources/Compute/Attribute/Rule/RuleContext.swift @@ -7,44 +7,65 @@ public struct RuleContext { } public func update(body: () -> Void) { - fatalError("not implemented") + AnyRuleContext(attribute: attribute.identifier).update(body: body) } public var value: Value { unsafeAddress { - fatalError("not implemented") + guard let result = __AGGraphGetOutputValue(Metadata(Value.self)) else { + preconditionFailure() + } + let pointer = result.assumingMemoryBound(to: Value.self) + return UnsafePointer(pointer) } - set { - fatalError("not implemented") + nonmutating set { + withUnsafePointer(to: newValue) { newValuePointer in + __AGGraphSetOutputValue(newValuePointer, Metadata(Value.self)) + } } } public var hasValue: Bool { - fatalError("not implemented") + let valuePointer = __AGGraphGetOutputValue(Metadata(Value.self)) + return valuePointer != nil } - public func changedValue(of attribute: Attribute, options: ValueOptions) -> (value: Value, changed: Bool) { - fatalError("not implemented") + public func changedValue(of input: Attribute, options: AGValueOptions) -> (value: Value, changed: Bool) { + let result = __AGGraphGetInputValue(attribute.identifier, input.identifier, options, Metadata(Value.self)) + return ( + result.value.assumingMemoryBound(to: Value.self).pointee, + result.changed + ) } - public func valueAndFlags(of attribute: Attribute, options: ValueOptions) -> ( - value: Value, flags: ChangedValueFlags + public func valueAndFlags(of input: Attribute, options: AGValueOptions) -> ( + value: Value, flags: AGChangedValueFlags ) { - fatalError("not implemented") + let result = __AGGraphGetInputValue(attribute.identifier, input.identifier, options, Metadata(Value.self)) + return ( + result.value.assumingMemoryBound(to: Value.self).pointee, + result.changed ? .changed : [] + ) } - public subscript(_ attribute: Attribute) -> T { + public subscript(_ input: Attribute) -> InputValue { unsafeAddress { - fatalError("not implemented") + return __AGGraphGetInputValue(attribute.identifier, input.identifier, [], Metadata(InputValue.self)) + .value + .assumingMemoryBound(to: InputValue.self) } } - public subscript(_ weakAttribute: WeakAttribute) -> T? { - fatalError("not implemented") + public subscript(_ weakInput: WeakAttribute) -> InputValue? { + return weakInput.attribute.map { input in + return self[input] + } } - public subscript(_ optionalAttribute: OptionalAttribute) -> T? { - fatalError("not implemented") + public subscript(_ optionalInput: OptionalAttribute) -> InputValue? { + return optionalInput.attribute.map { input in + return self[input] + } } } diff --git a/Sources/Compute/Attribute/Rule/StatefulRule.swift b/Sources/Compute/Attribute/Rule/StatefulRule.swift index cd0cbcd..b029676 100644 --- a/Sources/Compute/Attribute/Rule/StatefulRule.swift +++ b/Sources/Compute/Attribute/Rule/StatefulRule.swift @@ -16,39 +16,53 @@ extension StatefulRule { } public static func _updateDefault(_ default: UnsafeMutableRawPointer) { - fatalError("not implemented") + guard let initialValue = initialValue else { + return + } + withUnsafePointer(to: initialValue) { initialValuePointer in + __AGGraphSetOutputValue(initialValuePointer, Metadata(Value.self)) + } + } + + public static func _update(_ pointer: UnsafeMutableRawPointer, attribute: AnyAttribute) { + let rule = pointer.assumingMemoryBound(to: Self.self) + rule.pointee.updateValue() } } extension StatefulRule { - public static func _update(_ body: UnsafeMutableRawPointer, attribute: AnyAttribute) { - fatalError("not implemented") - } - public var bodyChanged: Bool { - fatalError("not implemented") + // guessing this is it + return __AGGraphCurrentAttributeWasModified() } public var value: Value { unsafeAddress { - fatalError("not implemented") + guard let result = __AGGraphGetOutputValue(Metadata(Value.self)) else { + preconditionFailure() + } + let pointer = result.assumingMemoryBound(to: Value.self) + return UnsafePointer(pointer) } - set { - fatalError("not implemented") + nonmutating set { + context.value = newValue } } public var hasValue: Bool { - fatalError("not implemented") + return context.hasValue } public var attribute: Attribute { - fatalError("not implemented") + guard let attribute = AnyAttribute.current else { + preconditionFailure() + } + return Attribute(identifier: attribute) } - + public var context: RuleContext { - fatalError("not implemented") + return RuleContext(attribute: attribute) } } diff --git a/Sources/Compute/Attribute/Weak/AnyWeakAttribute.swift b/Sources/Compute/Attribute/Weak/AnyWeakAttribute.swift index 8d170cb..144aefa 100644 --- a/Sources/Compute/Attribute/Weak/AnyWeakAttribute.swift +++ b/Sources/Compute/Attribute/Weak/AnyWeakAttribute.swift @@ -1,50 +1,52 @@ import ComputeCxx -public struct AnyWeakAttribute { +extension AnyWeakAttribute { public init(_ attribute: WeakAttribute) { - fatalError("not implemented") + self = attribute.base } public init(_ attribute: AnyAttribute?) { - fatalError("not implemented") - } - - public func unsafeCast(to type: Value.Type) -> WeakAttribute { - fatalError("not implemented") + self = __AGCreateWeakAttribute(attribute ?? .nil) } public var attribute: AnyAttribute? { get { - fatalError("not implemented") + let attribute = __AGWeakAttributeGetAttribute(self) + return attribute == .nil ? nil : attribute } set { - fatalError("not implemented") + self = AnyWeakAttribute(newValue) } } + public func unsafeCast(to type: Value.Type) -> WeakAttribute { + return WeakAttribute(base: self) + } + } -extension AnyWeakAttribute: CustomStringConvertible { +extension AnyWeakAttribute: @retroactive CustomStringConvertible { public var description: String { - fatalError("not implemented") + return attribute?.description ?? "nil" } } -extension AnyWeakAttribute: Equatable { - - public static func == (lhs: AnyWeakAttribute, rhs: AnyWeakAttribute) -> Bool { - fatalError("not implemented") +extension AnyWeakAttribute: @retroactive Equatable { + + public static func ==(lhs: AnyWeakAttribute, rhs: AnyWeakAttribute) -> Bool { + return lhs.attribute == rhs.attribute && lhs.subgraph_id == rhs.subgraph_id } - + } -extension AnyWeakAttribute: Hashable { - +extension AnyWeakAttribute: @retroactive Hashable { + public func hash(into hasher: inout Hasher) { - fatalError("not implemented") + hasher.combine(attribute) + hasher.combine(subgraph_id) } - + } diff --git a/Sources/Compute/Attribute/Weak/WeakAttribute.swift b/Sources/Compute/Attribute/Weak/WeakAttribute.swift index 3605cf4..8a111fc 100644 --- a/Sources/Compute/Attribute/Weak/WeakAttribute.swift +++ b/Sources/Compute/Attribute/Weak/WeakAttribute.swift @@ -7,51 +7,56 @@ public struct WeakAttribute { public init(base: AnyWeakAttribute) { self.base = base } - + public init() { - fatalError("not implemented") + base = AnyWeakAttribute(attribute: AnyAttribute(rawValue: 0), subgraph_id: 0) } - + public init(_ attribute: Attribute) { - fatalError("not implemented") + base = AnyWeakAttribute(attribute.identifier) } - + public init(_ attribute: Attribute?) { - fatalError("not implemented") + base = AnyWeakAttribute(attribute?.identifier) } - public func changedValue(options: ValueOptions) -> (value: Value, changed: Bool)? { - fatalError("not implemented") + public func changedValue(options: AGValueOptions) -> (value: Value, changed: Bool)? { + let result = __AGGraphGetWeakValue(base, options, Metadata(Value.self)) + return (result.value.assumingMemoryBound(to: Value.self).pointee, result.changed) } public var value: Value? { - fatalError("not implemented") + let result = __AGGraphGetWeakValue(base, [], Metadata(Value.self)) + return result.value.assumingMemoryBound(to: Value.self).pointee } public var attribute: Attribute? { get { - fatalError("not implemented") + return base.attribute?.unsafeCast(to: Value.self) } set { - fatalError("not implemented") + base.attribute = newValue?.identifier } } - + public var wrappedValue: Value? { - fatalError("not implemented") + return value } - + public var projectedValue: Attribute? { get { - fatalError("not implemented") + return attribute } set { - fatalError("not implemented") + attribute = newValue + } + _modify { + yield &attribute } } - + public subscript(dynamicMember keyPath: KeyPath) -> Attribute? { - fatalError("not implemented") + attribute?[keyPath: keyPath] } } @@ -59,7 +64,7 @@ public struct WeakAttribute { extension WeakAttribute: CustomStringConvertible { public var description: String { - fatalError("not implemented") + return base.description } } diff --git a/Sources/Compute/Graph/Graph.swift b/Sources/Compute/Graph/Graph.swift index 1f44862..e630c48 100644 --- a/Sources/Compute/Graph/Graph.swift +++ b/Sources/Compute/Graph/Graph.swift @@ -2,28 +2,94 @@ import ComputeCxx extension Graph { - public func onUpdate(_ handler: () -> Void) { - fatalError("not implemented") + @_extern(c, "AGGraphWithUpdate") + static func withUpdate(attribute: AnyAttribute, body: () -> Void) + +} + +extension Graph { + + @_extern(c, "AGGraphSearch") + static func search(attribute: AnyAttribute, options: AGSearchOptions, predicate: (AnyAttribute) -> Bool) -> Bool + +} + +extension Graph { + + @_extern(c, "AGGraphMutateAttribute") + static func mutateAttribute( + _ attribute: AnyAttribute, + type: Metadata, + invalidating: Bool, + body: (UnsafeMutableRawPointer) -> Void + ) + +} + +extension Graph { + + @_extern(c, "AGGraphSetUpdateCallback") + private static func setUpdateCallback(_ graph: UnsafeRawPointer, callback: (() -> Void)?) + + public static func setUpdateCallback(_ graph: Graph, callback: (() -> Void)?) { + Graph.setUpdateCallback(unsafeBitCast(graph, to: UnsafeRawPointer.self), callback: callback) } - public func onInvalidation(_ handler: (AnyAttribute) -> Void) { - fatalError("not implemented") + public func onUpdate(_ handler: @escaping () -> Void) { + Graph.setUpdateCallback(self, callback: handler) + } + +} + +extension Graph { + + @_extern(c, "AGGraphSetInvalidationCallback") + private static func setInvalidationCallback(_ graph: UnsafeRawPointer, callback: ((AnyAttribute) -> Void)?) + + public static func setInvalidationCallback(_ graph: Graph, callback: ((AnyAttribute) -> Void)?) { + Graph.setInvalidationCallback(unsafeBitCast(graph, to: UnsafeRawPointer.self), callback: callback) + } + + public func onInvalidation(_ handler: @escaping (AnyAttribute) -> Void) { + Graph.setInvalidationCallback(self, callback: handler) } public func withDeadline(_ deadline: UInt64, _ body: () -> T) -> T { - fatalError("not implemented") + let oldDeadline = __AGGraphGetDeadline(self) + __AGGraphSetDeadline(self, deadline) + let result = body() + __AGGraphSetDeadline(self, oldDeadline) + return result } public func withoutUpdate(_ body: () -> T) -> T { - fatalError("not implemented") + let previousUpdate = __AGGraphClearUpdate() + let result = body() + __AGGraphSetUpdate(previousUpdate) + return result } public func withoutSubgraphInvalidation(_ body: () -> T) -> T { - fatalError("not implemented") + let wasDeferringSubgraphInvalidation = __AGGraphBeginDeferringSubgraphInvalidation(self) + let result = body() + __AGGraphEndDeferringSubgraphInvalidation(self, wasDeferringSubgraphInvalidation) + return result } - public func withMainThreadHandler(_ body: (() -> Void) -> Void, do: () -> Void) { - fatalError("not implemented") + @_extern(c, "AGGraphWithMainThreadHandler") + private static func withMainThreadHandler( + _ graph: UnsafeRawPointer, + body: () -> Void, + mainThreadHandler: (() -> Void) -> Void + ) + + public func withMainThreadHandler( + _ mainThreadHandler: (() -> Void) -> Void, + do body: () -> Void + ) { + withUnsafePointer(to: self) { selfPointer in + Graph.withMainThreadHandler(selfPointer, body: body, mainThreadHandler: mainThreadHandler) + } } } @@ -31,56 +97,78 @@ extension Graph { extension Graph { public static func startProfiling() { - fatalError("not implemented") + __AGGraphStartProfiling(nil) } public static func stopProfiling() { - fatalError("not implemented") + __AGGraphStopProfiling(nil) } public static func markProfile(name: UnsafePointer) { - fatalError("not implemented") + __AGGraphMarkProfile(nil, name) } public static func resetProfile() { - fatalError("not implemented") + __AGGraphResetProfile(nil) } } extension Graph { - public func addTraceEvent(_ event: UnsafePointer, value: T) { - fatalError("not implemented") + public func addTraceEvent(_ event: UnsafePointer, context: UnsafePointer) { + __AGGraphAddTraceEvent(self, event, context, Metadata(T.self)) } - public func addTraceEvent(_ event: UnsafePointer, context: UnsafePointer) { - fatalError("not implemented") + public func addTraceEvent(_ event: UnsafePointer, value: T) { + withUnsafePointer(to: value) { valuePointer in + __AGGraphAddTraceEvent(self, event, valuePointer, Metadata(T.self)) + } } } +extension Graph { + + public var mainUpdates: Int { numericCast(counter(for: .mainThreadUpdateCount)) } + +} + extension Graph { public func print(includeValues: Bool) { - fatalError("not implemented") + Swift.print(graphvizDescription(includeValues: includeValues)) } - public func archiveJSON(name: String?) { - fatalError("not implemented") + public static func archiveJSON(name: String?) { + __AGGraphArchiveJSON(name?.cString(using: .utf8)) } public func graphvizDescription(includeValues: Bool) -> String { - fatalError("not implemented") + let result = Graph.description( + self, + options: [AGDescriptionFormat: "graph/dot", AGDescriptionIncludeValues: includeValues] as CFDictionary + ).takeUnretainedValue() + guard let description = result as? String else { + preconditionFailure() + } + return description } public static func printStack(maxFrames: Int) { - fatalError("not implemented") + Swift.print(stackDescription(maxFrames: maxFrames)) } public static func stackDescription(maxFrames: Int) -> String { - fatalError("not implemented") + let result = Graph.description( + nil, + options: [AGDescriptionFormat: "stack/text", AGDescriptionMaxFrames: maxFrames] as CFDictionary + ).takeUnretainedValue() + guard let description = result as? String else { + preconditionFailure() + } + return description } } @@ -88,7 +176,7 @@ extension Graph { extension Graph: @retroactive Equatable { public static func == (_ lhs: Graph, _ rhs: Graph) -> Bool { - fatalError("not implemented") + return lhs.counter(for: .graphID) == rhs.counter(for: .graphID) } } diff --git a/Sources/Compute/Graph/Subgraph.swift b/Sources/Compute/Graph/Subgraph.swift index 00f381c..4f8ec11 100644 --- a/Sources/Compute/Graph/Subgraph.swift +++ b/Sources/Compute/Graph/Subgraph.swift @@ -2,32 +2,51 @@ import ComputeCxx extension Subgraph { - public func addObserver(_ observer: () -> Void) -> Int { - fatalError("not implemented") + @_extern(c, "AGSubgraphAddObserver") + private static func addObserver(_ subgraph: UnsafeRawPointer, observer: () -> Void) -> Int + + func addObserver(_ observer: () -> Void) -> Int { + return Subgraph.addObserver(unsafeBitCast(self, to: UnsafeRawPointer.self), observer: observer) } public func apply(_ body: () -> T) -> T { - fatalError("not implemented") + let previousSubgraph = Subgraph.current + let previousUpdate = __AGGraphClearUpdate() + defer { + Subgraph.current = previousSubgraph + __AGGraphSetUpdate(previousUpdate) + } + Subgraph.current = self + return body() } - public func forEach(_ flags: AttributeFlags, _ body: (AnyAttribute) -> Void) { - fatalError("not implemented") + @_extern(c, "AGSubgraphApply") + private static func apply(_ subgraph: UnsafeRawPointer, flags: AGAttributeFlags, body: (AnyAttribute) -> Void) + + public func forEach(_ flags: AGAttributeFlags, _ body: (AnyAttribute) -> Void) { + Subgraph.apply(unsafeBitCast(self, to: UnsafeRawPointer.self), flags: flags, body: body) } } extension Subgraph { - public func addTreeValue(_ attribute: Attribute, forKey key: UnsafePointer, flags: UInt32) { - fatalError("not implemented") + public static func addTreeValue(_ value: Attribute, forKey key: UnsafePointer, flags: UInt32) { + if shouldRecordTree { + __AGSubgraphAddTreeValue(value.identifier, Metadata(Value.self), key, flags) + } } - public func beginTreeElement(value: Attribute, flags: UInt32) { - fatalError("not implemented") + public static func beginTreeElement(value: Attribute, flags: UInt32) { + if shouldRecordTree { + __AGSubgraphBeginTreeElement(value.identifier, Metadata(Value.self), flags) + } } - public func endTreeElement(value: Attribute) { - fatalError("not implemented") + public static func endTreeElement(value: Attribute) { + if shouldRecordTree { + __AGSubgraphEndTreeElement(value.identifier) + } } } diff --git a/Sources/Compute/Graph/TreeElement.swift b/Sources/Compute/Graph/TreeElement.swift index 2394072..70cc517 100644 --- a/Sources/Compute/Graph/TreeElement.swift +++ b/Sources/Compute/Graph/TreeElement.swift @@ -1,35 +1,35 @@ import ComputeCxx -public struct TreeElement {} - extension TreeElement { public var value: AnyAttribute? { - fatalError("not implemented") + let result = __AGTreeElementGetValue(self) + return result == .nil ? nil : result } } -struct Nodes: Sequence, IteratorProtocol { +extension Nodes: @retroactive Sequence, @retroactive IteratorProtocol { - public func next() -> AnyAttribute? { - fatalError("not implemented") + public mutating func next() -> AnyAttribute? { + let result = __AGTreeElementGetNextNode(&self) + return result == .nil ? nil : result } } -struct Children: Sequence, IteratorProtocol { +extension Children: @retroactive Sequence, @retroactive IteratorProtocol { public mutating func next() -> TreeElement? { - fatalError("not implemented") + return __AGTreeElementGetNextChild(&self) } } -struct Values: Sequence, IteratorProtocol { +extension Values: @retroactive Sequence, @retroactive IteratorProtocol { - public func next() -> TreeElement? { - fatalError("not implemented") + public func next() -> TreeValue? { + return __AGTreeElementGetNextValue(self) } } diff --git a/Sources/Compute/Runtime/CompareValues.swift b/Sources/Compute/Runtime/CompareValues.swift index 3597d25..1b112f1 100644 --- a/Sources/Compute/Runtime/CompareValues.swift +++ b/Sources/Compute/Runtime/CompareValues.swift @@ -8,8 +8,7 @@ extension AGComparisonOptions { } -public func compareValues(_ lhs: Value, _ rhs: Value, mode: AGComparisonMode = .equatableAlways) -> Bool -{ +public func compareValues(_ lhs: Value, _ rhs: Value, mode: AGComparisonMode = .equatableAlways) -> Bool { return compareValues(lhs, rhs, options: AGComparisonOptions(mode: mode)) } diff --git a/Sources/Compute/Runtime/Tuple.swift b/Sources/Compute/Runtime/Tuple.swift index 7180386..150d065 100644 --- a/Sources/Compute/Runtime/Tuple.swift +++ b/Sources/Compute/Runtime/Tuple.swift @@ -3,7 +3,7 @@ public func withUnsafeTuple(of type: TupleType, count: Int, body: (UnsafeMutable } extension TupleType { - + @_extern(c, "AGTupleWithBuffer") fileprivate static func withUnsafeTuple(of type: TupleType, count: Int, body: (UnsafeMutableTuple) -> Void) diff --git a/Sources/ComputeCxx/Array/ArrayRef.h b/Sources/ComputeCxx/Array/ArrayRef.h new file mode 100644 index 0000000..1f10a3d --- /dev/null +++ b/Sources/ComputeCxx/Array/ArrayRef.h @@ -0,0 +1,73 @@ +#pragma once + +#include + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +template class ArrayRef { + public: + using value_type = T; + using pointer = value_type *; + using const_pointer = const value_type *; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using size_type = size_t; + using difference_type = ptrdiff_t; + + private: + const T *_Nullable _data = nullptr; + size_type _size = 0; + + public: + ArrayRef() = default; + ArrayRef(T *_Nullable data, size_t size) : _data(data), _size(size){}; + + // Element access + + const T &operator[](size_t pos) const { + assert(pos < _size); + return _data[pos]; + }; + + const T &front() const { + assert(!empty()); + return _data[0]; + }; + const T &back() const { + assert(!empty()); + return _data[_size - 1]; + }; + + const T *_Nonnull data() const { return _data; }; + + // Iterators + + iterator _Nullable begin() { return _data; }; + iterator _Nullable end() { return _data + _size; }; + const_iterator _Nullable cbegin() const { return _data; }; + const_iterator _Nullable cend() const { return _data + _size; }; + const_iterator _Nullable begin() const { return cbegin(); }; + const_iterator _Nullable end() const { return cend(); }; + + reverse_iterator rbegin() { return std::reverse_iterator(end()); }; + reverse_iterator rend() { return std::reverse_iterator(begin()); }; + const_reverse_iterator crbegin() const { return std::reverse_iterator(cend()); }; + const_reverse_iterator crend() const { return std::reverse_iterator(cbegin()); }; + const_reverse_iterator rbegin() const { return crbegin(); }; + const_reverse_iterator rend() const { return crend(); }; + + // Capacity + + bool empty() const { return _size == 0; }; + size_type size() const { return _size; }; +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/AGAttribute.cpp b/Sources/ComputeCxx/Attribute/AGAttribute.cpp new file mode 100644 index 0000000..665a1ca --- /dev/null +++ b/Sources/ComputeCxx/Attribute/AGAttribute.cpp @@ -0,0 +1,5 @@ +#include "AGAttribute.h" + +#include "AttributeID.h" + +const AGAttribute AGAttributeNil = AGAttribute(AG::AttributeIDNil); diff --git a/Sources/ComputeCxx/Attribute/AGAttribute.h b/Sources/ComputeCxx/Attribute/AGAttribute.h index 5a6b5a6..63e45f2 100644 --- a/Sources/ComputeCxx/Attribute/AGAttribute.h +++ b/Sources/ComputeCxx/Attribute/AGAttribute.h @@ -4,6 +4,8 @@ #include #include "AGSwiftSupport.h" +#include "Closure/AGClosure.h" +#include "Swift/AGType.h" CF_ASSUME_NONNULL_BEGIN @@ -11,6 +13,60 @@ CF_EXTERN_C_BEGIN typedef uint32_t AGAttribute AG_SWIFT_STRUCT AG_SWIFT_NAME(AnyAttribute); +CF_EXPORT +const AGAttribute AGAttributeNil; + +// TODO: validate where these come from +typedef CF_OPTIONS(uint8_t, AGAttributeFlags) { + AGAttributeFlagsDefault = 0, + AGAttributeFlagsActive = 1 << 0, + AGAttributeFlagsRemovable = 1 << 1, + AGAttributeFlagsInvalidatable = 1 << 2, +}; + +typedef CF_OPTIONS(uint32_t, AGAttributeTypeFlags) { + AGAttributeTypeFlagsNone = 0, + AGAttributeTypeFlagsOption4 = 4, + AGAttributeTypeFlagsOption8 = 8, + AGAttributeTypeFlagsOption16 = 16, +}; + +typedef struct AGAttributeVTable { + void *callback0; + void *callback1; + void *callback2; + void *callback3; + void *callback4; + void *callback5; +} AGAttributeVTable; + +struct AGClosureStorage2 { + void *a; + void *b; +}; + +typedef struct AGAttributeType { + AGTypeID typeID; + AGTypeID valueTypeID; + + AGClosureStorage2 update_function; + + AGAttributeVTable *_Nullable callbacks; + AGAttributeTypeFlags flags; + + // set after construction + uint32_t attribute_offset; + const unsigned char *_Nullable layout; + + AGTypeID initial_body_type_id; + AGTypeID initial_body_witness_table; +} AGAttributeType; + +typedef struct AGAttributeInfo { + const AGAttributeType *type; + const void *body; +} AGAttributeInfo; + CF_EXTERN_C_END CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/AGWeakAttribute.cpp b/Sources/ComputeCxx/Attribute/AGWeakAttribute.cpp new file mode 100644 index 0000000..59efbbf --- /dev/null +++ b/Sources/ComputeCxx/Attribute/AGWeakAttribute.cpp @@ -0,0 +1,20 @@ +#include "AGWeakAttribute.h" + +#include "Attribute/AttributeID.h" +#include "Attribute/WeakAttributeID.h" +#include "Data/Zone.h" + +AGWeakAttribute AGCreateWeakAttribute(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.has_value()) { + return AG::WeakAttributeID(attribute_id, 0).to_cf(); + } + + auto zone_id = attribute_id.page().zone->info().zone_id(); + return AG::WeakAttributeID(attribute_id, zone_id).to_cf(); +} + +AGAttribute AGWeakAttributeGetAttribute(AGWeakAttribute attribute) { + auto attribute_id = AG::WeakAttributeID::from_cf(attribute); + return attribute_id.evaluate(); +} diff --git a/Sources/ComputeCxx/Attribute/AGWeakAttribute.h b/Sources/ComputeCxx/Attribute/AGWeakAttribute.h new file mode 100644 index 0000000..fddde8f --- /dev/null +++ b/Sources/ComputeCxx/Attribute/AGWeakAttribute.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "AGAttribute.h" +#include "AGSwiftSupport.h" + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +typedef struct AGWeakAttribute { + AGAttribute attribute; + uint32_t subgraph_id; +} AG_SWIFT_NAME(AnyWeakAttribute) AGWeakAttribute; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGWeakAttribute AGCreateWeakAttribute(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGWeakAttributeGetAttribute(AGWeakAttribute attribute); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/AttributeID.cpp b/Sources/ComputeCxx/Attribute/AttributeID.cpp index 9dde0a2..44c65a4 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID.cpp +++ b/Sources/ComputeCxx/Attribute/AttributeID.cpp @@ -11,9 +11,15 @@ namespace AG { +AttributeID AttributeIDNil = AttributeID::from_storage(2); + +RelativeAttributeID AttributeID::to_relative() const { + return RelativeAttributeID(((_value & ~KindMask) - page_ptr().offset()) | kind()); +} + std::optional AttributeID::size() const { if (is_direct()) { - const AttributeType &attribute_type = subgraph()->graph().attribute_type(to_node().type_id()); + const AttributeType &attribute_type = subgraph()->graph()->attribute_type(to_node().type_id()); size_t size = attribute_type.value_metadata().vw_size(); return std::optional(size); } @@ -27,7 +33,7 @@ bool AttributeID::traverses(AttributeID other, TraversalOptions options) const { if (!is_indirect()) { return *this == other; } - + if (with_kind(Kind::Indirect) == other) { return true; } @@ -56,7 +62,6 @@ OffsetAttributeID AttributeID::resolve_slow(TraversalOptions options) const { } auto indirect_node = to_indirect_node(); - if (indirect_node.is_mutable()) { if (options & TraversalOptions::SkipMutableReference) { return OffsetAttributeID(result, offset); @@ -67,7 +72,7 @@ OffsetAttributeID AttributeID::resolve_slow(TraversalOptions options) const { if (dependency) { auto subgraph = dependency.subgraph(); if (subgraph) { - subgraph->graph().update_attribute(dependency, false); + subgraph->graph()->update_attribute(dependency, false); } } } @@ -78,7 +83,7 @@ OffsetAttributeID AttributeID::resolve_slow(TraversalOptions options) const { if (options & TraversalOptions::AssertNotNil) { precondition_failure("invalid indirect ref: %u", _value); } - return OffsetAttributeID(AttributeID::make_nil()); + return OffsetAttributeID(AttributeIDNil); } } diff --git a/Sources/ComputeCxx/Attribute/AttributeID.h b/Sources/ComputeCxx/Attribute/AttributeID.h index ebe9c0b..35ce685 100644 --- a/Sources/ComputeCxx/Attribute/AttributeID.h +++ b/Sources/ComputeCxx/Attribute/AttributeID.h @@ -5,10 +5,10 @@ #include #include +#include "AGAttribute.h" #include "Data/Page.h" #include "Data/Pointer.h" #include "Data/Zone.h" -#include "Subgraph/Subgraph.h" CF_ASSUME_NONNULL_BEGIN @@ -17,14 +17,45 @@ namespace AG { class Subgraph; class Node; class IndirectNode; +class MutableIndirectNode; class OffsetAttributeID; +class RelativeAttributeID; + +enum TraversalOptions : uint32_t { + None = 0, + + /// Updates indirect node dependencies prior to traversing. + UpdateDependencies = 1 << 0, + + /// Guarantees the resolved attribute is not nil, otherwise traps. + AssertNotNil = 1 << 1, + + /// When set, only statically evaluable references are traversed. + /// The returned attribute may be a mutable indirect node. + SkipMutableReference = 1 << 2, + + /// When set, the returned offset will be 0 if no indirection was traversed, + /// otherwise it will be the the actual offset + 1. + ReportIndirectionInOffset = 1 << 3, + + /// When set and `AssertNotNil` is not also set, returns the nil attribute + /// if any weak references evaluate to nil. + EvaluateWeakReferences = 1 << 4, +}; +inline TraversalOptions &operator|=(TraversalOptions &lhs, TraversalOptions rhs) { + lhs = TraversalOptions(uint32_t(lhs) | uint32_t(rhs)); + return lhs; +} +inline TraversalOptions operator|(TraversalOptions lhs, TraversalOptions rhs) { return (lhs |= rhs); } class AttributeID { + friend RelativeAttributeID; + private: static constexpr uint32_t KindMask = 0x3; uint32_t _value; - AttributeID(uint32_t value) : _value(value) {}; + explicit constexpr AttributeID(uint32_t value) : _value(value) {}; public: enum Kind : uint32_t { @@ -32,64 +63,108 @@ class AttributeID { Indirect = 1 << 0, NilAttribute = 1 << 1, }; - enum TraversalOptions : uint32_t { - None = 0, - /// Updates indirect node dependencies prior to traversing. - UpdateDependencies = 1 << 0, + explicit constexpr AttributeID() : _value(0) {}; + explicit AttributeID(data::ptr node) : _value(node.offset() | Kind::Direct) {}; + explicit AttributeID(data::ptr indirect_node) : _value(indirect_node.offset() | Kind::Indirect) {}; + explicit AttributeID(data::ptr indirect_node) + : _value(indirect_node.offset() | Kind::Indirect) {}; + + operator AGAttribute() const { return _value; } + static constexpr AttributeID from_storage(uint32_t value) { return AttributeID(value); } + + // + + // MARK: Operators + + bool operator==(const AttributeID &other) const { return _value == other._value; } + bool operator!=(const AttributeID &other) const { return _value != other._value; } + + bool operator<(const AttributeID &other) const { return _value < other._value; } - /// Guarantees the resolved attribute is not nil, otherwise traps. - AssertNotNil = 1 << 1, + explicit operator bool() const { return _value != 0; } - /// When set, only statically evaluable references are traversed. - /// The returned attribute may be a mutable indirect node. - SkipMutableReference = 1 << 2, + // MARK: Accessing zone data - /// When set, the returned offset will be 0 if no indirection was traversed, - /// otherwise it will be the the actual offset + 1. - ReportIndirectionInOffset = 1 << 3, + // data::ptr as_ptr() const { return data::ptr(_value); }; - /// When set and `AssertNotNil` is not also set, returns the nil attribute - /// if any weak references evaluate to nil. - EvaluateWeakReferences = 1 << 4, + // uint32_t value() const { return _value; } + // explicit operator bool() const { return _value == 0; }; + + data::page &page() const { + assert(_value); + return *data::ptr(_value).page_ptr(); }; - explicit AttributeID(data::ptr node) : _value(node.offset() | Kind::Direct) {}; - explicit AttributeID(data::ptr indirect_node) : _value(indirect_node.offset() | Kind::Indirect) {}; - static AttributeID make_nil() { return AttributeID(Kind::NilAttribute); }; + data::ptr page_ptr() const { return data::ptr(_value).page_ptr(); }; + + void validate_data_offset() const { data::ptr(_value).assert_valid(); }; - operator bool() const { return _value == 0; }; + // MARK: Relative + + RelativeAttributeID to_relative() const; + + // MARK: Accessing graph data Kind kind() const { return Kind(_value & KindMask); }; AttributeID with_kind(Kind kind) const { return AttributeID((_value & ~KindMask) | kind); }; bool is_direct() const { return kind() == Kind::Direct; }; bool is_indirect() const { return kind() == Kind::Indirect; }; - bool is_nil() const { return kind() == Kind::NilAttribute; }; + bool is_nil() const { return kind() == Kind::NilAttribute; }; // TODO: return true if whole thing is zero? - const Node &to_node() const { - assert(is_direct()); - return *data::ptr(_value & ~KindMask); - }; + bool has_value() const { return (_value & ~KindMask) != 0; } - const IndirectNode &to_indirect_node() const { + template data::ptr to_ptr() const { return data::ptr(_value & ~KindMask); } + template <> data::ptr to_ptr() const { + assert(is_direct()); + return data::ptr(_value & ~KindMask); + } + template <> data::ptr to_ptr() const { assert(is_indirect()); - return *data::ptr(_value & ~KindMask); - }; + return data::ptr(_value & ~KindMask); + } + template <> data::ptr to_ptr() const { + assert(is_indirect()); + return data::ptr(_value & ~KindMask); + } - Subgraph *_Nullable subgraph() const { return static_cast(page_ptr()->zone); } + Node &to_node() const { return *to_ptr(); }; + IndirectNode &to_indirect_node() const { return *to_ptr(); }; - data::ptr page_ptr() const { return data::ptr(_value).page_ptr(); }; + Subgraph *_Nullable subgraph() const { return reinterpret_cast(page().zone); } + + // MARK: Value metdata - // Value metadata std::optional size() const; - // Graph traversal + // MARK: Graph traversal + bool traverses(AttributeID other, TraversalOptions options) const; + OffsetAttributeID resolve(TraversalOptions options) const; OffsetAttributeID resolve_slow(TraversalOptions options) const; }; +class RelativeAttributeID { + private: + uint16_t _value; + + public: + constexpr RelativeAttributeID() : _value(0) {}; + constexpr RelativeAttributeID(nullptr_t) : _value(0) {}; + constexpr RelativeAttributeID(uint16_t value) : _value(value) {}; + + uint16_t value() const { return _value; } + + bool operator==(const RelativeAttributeID &other) const { return _value == other._value; } + bool operator!=(const RelativeAttributeID &other) const { return _value != other._value; } + + AttributeID resolve(data::ptr page_ptr) { return AttributeID(page_ptr.offset() + _value); } +}; + +extern AttributeID AttributeIDNil; + } // namespace AG CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/AttributeIDList.h b/Sources/ComputeCxx/Attribute/AttributeIDList.h new file mode 100644 index 0000000..098a5bc --- /dev/null +++ b/Sources/ComputeCxx/Attribute/AttributeIDList.h @@ -0,0 +1,77 @@ +#pragma once + +#include "Data/Page.h" +#include "Node/IndirectNode.h" +#include "Node/Node.h" + +namespace AG { + +class AttributeIDIterator { + private: + data::ptr _page; + RelativeAttributeID _current; + + public: + AttributeIDIterator(data::ptr page, RelativeAttributeID current) : _page(page), _current(current) {} + + bool operator==(const AttributeIDIterator &other) const { + return _page == other._page && _current == other._current; + } + bool operator!=(const AttributeIDIterator &other) const { + return _page != other._page || _current != other._current; + } + + AttributeID operator*() { + assert(_page); + return _current.resolve(_page); + } + + AttributeIDIterator &operator++() { + assert(_page); + + AttributeID attribute_id = _current.resolve(_page); + if (attribute_id.is_direct()) { + _current = attribute_id.to_node().relative_offset(); + } else if (attribute_id.is_indirect()) { + _current = attribute_id.to_indirect_node().relative_offset(); + } else { + _page = nullptr; + _current = nullptr; + } + return *this; + } +}; + +class AttributeIDList { + public: + virtual AttributeIDIterator begin() {}; + virtual AttributeIDIterator end() {}; +}; + +class AttributeIDList1 : public AttributeIDList { + private: + data::ptr _page; + + public: + AttributeIDList1(data::ptr page) : _page(page) {} + + virtual AttributeIDIterator begin() override { + return AttributeIDIterator(_page, RelativeAttributeID(_page->first_child_1)); + } + virtual AttributeIDIterator end() override { return AttributeIDIterator(nullptr, nullptr); } +}; + +class AttributeIDList2 : public AttributeIDList { + private: + data::ptr _page; + + public: + AttributeIDList2(data::ptr page) : _page(page) {} + + virtual AttributeIDIterator begin() override { + return AttributeIDIterator(_page, RelativeAttributeID(_page->first_child_2)); + } + virtual AttributeIDIterator end() override { return AttributeIDIterator(nullptr, nullptr); } +}; + +} // namespace AG diff --git a/Sources/ComputeCxx/Attribute/AttributeType.cpp b/Sources/ComputeCxx/Attribute/AttributeType.cpp new file mode 100644 index 0000000..6b2cfa8 --- /dev/null +++ b/Sources/ComputeCxx/Attribute/AttributeType.cpp @@ -0,0 +1,14 @@ +#include "AttributeType.h" + +#include "Attribute/Node/Node.h" + +namespace AG { + +void AttributeType::update_attribute_offset() { + uint32_t alignment_mask = uint32_t(self_metadata().getValueWitnesses()->getAlignmentMask()); + _attribute_offset = (sizeof(Node) + alignment_mask) & ~alignment_mask; +} + +static_assert(sizeof(Node) == 0x1c); + +} // namespace AG diff --git a/Sources/ComputeCxx/Attribute/AttributeType.h b/Sources/ComputeCxx/Attribute/AttributeType.h index 814ed47..da4fd30 100644 --- a/Sources/ComputeCxx/Attribute/AttributeType.h +++ b/Sources/ComputeCxx/Attribute/AttributeType.h @@ -2,48 +2,114 @@ #include +#include "Attribute/Node/Node.h" +#include "Comparison/AGComparison.h" +#include "Comparison/LayoutDescriptor.h" #include "Swift/Metadata.h" CF_ASSUME_NONNULL_BEGIN namespace AG { +class AttributeID; class AttributeType; class AttributeVTable { public: - enum Flags : uint8_t { - HasDestroySelf = 1 << 2, - }; - - using Callback = void (*)(AttributeType *attribute_type, void *body); + using Callback = void (*)(const AttributeType *attribute_type, void *body); + using DescriptionCallback = CFStringRef _Nonnull (*)(const AttributeType *attribute_type, void *body); + Callback _unknown_0x00; + Callback _unknown_0x08; Callback destroy_self; + DescriptionCallback _self_description; + DescriptionCallback _value_description; + Callback _update_stack_callback; // maybe initialize value }; class AttributeType { + public: + enum Flags : uint32_t { + ComparisonModeMask = 0x3, + + HasDestroySelf = 1 << 2, // 0x04 + MainThread = 1 << 3, // 0x08 + UseGraphAsInitialValue = 1 << 4, // 0x10 + Unknown0x20 = 1 << 5, // 0x20 // used in update_main_refs + }; + + using UpdateFunction = void (*)(const void *context, void *body, AttributeID attribute); + private: swift::metadata *_self_metadata; swift::metadata *_value_metadata; - void *_field1; - void *_field2; - AttributeVTable *_v_table; - uint8_t _v_table_flags; + UpdateFunction _update_function; + void *_update_context; + AttributeVTable *_vtable; + Flags _flags; + + // set after construction uint32_t _attribute_offset; + ValueLayout _layout; public: + class deleter {}; + const swift::metadata &self_metadata() const { return *_self_metadata; }; const swift::metadata &value_metadata() const { return *_value_metadata; }; + Flags flags() const { return _flags; }; + + bool main_thread() const { return _flags & Flags::MainThread; }; + bool use_graph_as_initial_value() const { return _flags & Flags::UseGraphAsInitialValue; }; + bool unknown_0x20() const { return _flags & Flags::Unknown0x20; }; + /// Returns the offset in bytes from a Node to the attribute body, /// aligned to the body's alignment. uint32_t attribute_offset() const { return _attribute_offset; }; + void update_attribute_offset(); + + ValueLayout layout() const { return _layout; }; + void set_layout(ValueLayout layout) { _layout = layout; }; + AGComparisonMode comparison_mode() const { return AGComparisonMode(_flags & Flags::ComparisonModeMask); }; + void update_layout() { + if (!_layout) { + auto comparison_mode = AGComparisonMode(_flags & Flags::ComparisonModeMask); + _layout = LayoutDescriptor::fetch(value_metadata(), comparison_mode, 1); + } + }; + + void perform_update(void *body, AttributeID attribute) const { + _update_function(_update_context, body, attribute); + }; // V table methods - void v_destroy_self(void *body) { - if (_v_table_flags & AttributeVTable::Flags::HasDestroySelf) { - _v_table->destroy_self(this, body); + void vt_destroy_self(void *body) { + // TODO: does this check _flags or the callback itself? + if (_flags & Flags::HasDestroySelf) { + _vtable->destroy_self(this, body); + } + } + + CFStringRef _Nullable vt_self_description(void *self_data) const { + if (auto callback = _vtable->_self_description) { + return callback(this, self_data); + } + return nullptr; + } + CFStringRef _Nullable vt_value_description(void *value) const { + if (auto callback = _vtable->_value_description) { + return callback(this, value); } + return nullptr; + } + + AttributeVTable::DescriptionCallback _Nullable vt_get_self_description_callback() const { + return _vtable->_self_description; + } + AttributeVTable::DescriptionCallback _Nullable vt_get_value_description_callback() const { + return _vtable->_value_description; } + AttributeVTable::Callback vt_get_update_stack_callback() const { return _vtable->_update_stack_callback; } }; } // namespace AG diff --git a/Sources/ComputeCxx/Attribute/Node/Edge.h b/Sources/ComputeCxx/Attribute/Node/Edge.h new file mode 100644 index 0000000..4b60158 --- /dev/null +++ b/Sources/ComputeCxx/Attribute/Node/Edge.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Array/ArrayRef.h" +#include "Attribute/AttributeID.h" + +namespace AG { + +class Node; + +struct InputEdge { + enum Flags : uint8_t { + Unprefetched = 1 << 0, + Unknown1 = 1 << 1, + AlwaysEnabled = 1 << 2, + Changed = 1 << 3, // set when node is dirty + Unknown4 = 1 << 4, + }; + + struct Comparator { + AttributeID attribute; + uint8_t flags_mask; + uint8_t flags; + bool match(InputEdge &input) { return input.value == attribute && (input._flags & flags_mask) == flags; } + }; + + AttributeID value; + uint8_t _flags; + + bool is_unprefetched() { return _flags & Flags::Unprefetched; }; + void set_unprefetched(bool value) { _flags = (_flags & ~Flags::Unprefetched) | (value ? Flags::Unprefetched : 0); }; + + bool is_unknown1() { return _flags & Flags::Unknown1; }; + void set_unknown1(bool value) { _flags = (_flags & ~Flags::Unknown1) | (value ? Flags::Unknown1 : 0); }; + + bool is_always_enabled() { return _flags & Flags::AlwaysEnabled; }; + void set_always_enabled(bool value) { + _flags = (_flags & ~Flags::AlwaysEnabled) | (value ? Flags::AlwaysEnabled : 0); + }; + + bool is_changed() { return _flags & Flags::Changed; }; + void set_changed(bool value) { _flags = (_flags & ~Flags::Changed) | (value ? Flags::Changed : 0); }; + + bool is_unknown4() { return _flags & Flags::Unknown4; }; + void set_unknown4(bool value) { _flags = (_flags & ~Flags::Unknown4) | (value ? Flags::Unknown4 : 0); }; + + bool operator<(const InputEdge &other) const noexcept { + return value != other.value ? value < other.value : _flags < other._flags; + }; +}; + +using ConstInputEdgeArrayRef = const ArrayRef; + +struct OutputEdge { + AttributeID value; +}; + +using ConstOutputEdgeArrayRef = ArrayRef; + +} // namespace AG diff --git a/Sources/ComputeCxx/Attribute/Node/IndirectNode.cpp b/Sources/ComputeCxx/Attribute/Node/IndirectNode.cpp index 844adda..ed31673 100644 --- a/Sources/ComputeCxx/Attribute/Node/IndirectNode.cpp +++ b/Sources/ComputeCxx/Attribute/Node/IndirectNode.cpp @@ -4,14 +4,19 @@ namespace AG { +MutableIndirectNode &IndirectNode::to_mutable() { + assert(is_mutable()); + return static_cast(*this); +} + const MutableIndirectNode &IndirectNode::to_mutable() const { assert(is_mutable()); return static_cast(*this); } -void IndirectNode::modify(WeakAttributeID source, size_t size) { +void IndirectNode::modify(WeakAttributeID source, uint32_t offset) { _source = source; - _info.size = uint32_t(size); + _info.offset = offset; } } // namespace AG diff --git a/Sources/ComputeCxx/Attribute/Node/IndirectNode.h b/Sources/ComputeCxx/Attribute/Node/IndirectNode.h index fb5bba3..99cd391 100644 --- a/Sources/ComputeCxx/Attribute/Node/IndirectNode.h +++ b/Sources/ComputeCxx/Attribute/Node/IndirectNode.h @@ -4,6 +4,8 @@ #include "Attribute/AttributeID.h" #include "Attribute/WeakAttributeID.h" +#include "Data/Vector.h" +#include "Edge.h" CF_ASSUME_NONNULL_BEGIN @@ -12,43 +14,81 @@ namespace AG { class MutableIndirectNode; class IndirectNode { + public: + // TODO: is there special treatment of lowest bit? + static constexpr uint16_t MaximumOffset = 0x3ffffffe; // 30 bits - 1 + private: struct Info { unsigned int is_mutable : 1; - unsigned int traverses_graph_contexts : 1; + unsigned int traverses_contexts : 1; unsigned int offset : 30; - unsigned int size : 32; }; - static_assert(sizeof(Info) == 8); - static constexpr uint32_t InvalidSize = 0xffff; + static_assert(sizeof(Info) == 4); + static constexpr uint16_t InvalidSize = 0xffff; WeakAttributeID _source; Info _info; + uint16_t _size; + RelativeAttributeID _relative_offset; public: + IndirectNode(WeakAttributeID source, bool traverses_contexts, uint32_t offset, uint16_t size) : _source(source) { + _info.is_mutable = false; + _info.traverses_contexts = traverses_contexts; + _info.offset = offset; + _size = size; + } + + const WeakAttributeID &source() const { return _source; }; + bool is_mutable() const { return _info.is_mutable; }; + MutableIndirectNode &to_mutable(); const MutableIndirectNode &to_mutable() const; - bool traverses_graph_contexts() const { return _info.traverses_graph_contexts; }; + void set_traverses_contexts(bool value) { _info.traverses_contexts = value; }; + bool traverses_contexts() const { return _info.traverses_contexts; }; uint32_t offset() const { return _info.offset; }; + bool has_size() const { return _size != InvalidSize; }; std::optional size() const { - return _info.size != InvalidSize ? std::optional(size_t(_info.size)) : std::optional(); + return _size != InvalidSize ? std::optional(size_t(_size)) : std::optional(); }; - const WeakAttributeID &source() const { return _source; }; - - void modify(WeakAttributeID source, size_t size); + RelativeAttributeID relative_offset() const { return _relative_offset; }; + void set_relative_offset(RelativeAttributeID relative_offset) { _relative_offset = relative_offset; }; + + void modify(WeakAttributeID source, uint32_t offset); }; +static_assert(sizeof(IndirectNode) == 0x10); + class MutableIndirectNode : public IndirectNode { private: AttributeID _dependency; + data::vector _outputs; + WeakAttributeID _initial_source; + uint32_t _initial_offset; public: + MutableIndirectNode(WeakAttributeID source, bool traverses_contexts, uint32_t offset, uint16_t size, + WeakAttributeID initial_source, uint32_t initial_offset) + : IndirectNode(source, traverses_contexts, offset, size), _dependency(), _initial_source(initial_source), + _initial_offset(initial_offset) { + + }; + const AttributeID &dependency() const { return _dependency; }; + void set_dependency(const AttributeID &dependency) { _dependency = dependency; }; + + WeakAttributeID initial_source() { return _initial_source; }; + uint32_t initial_offset() { return _initial_offset; }; + + data::vector outputs() const { return _outputs; }; }; +static_assert(sizeof(MutableIndirectNode) == 0x28); + } // namespace AG CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/Node/Node.cpp b/Sources/ComputeCxx/Attribute/Node/Node.cpp index a886190..3e943cc 100644 --- a/Sources/ComputeCxx/Attribute/Node/Node.cpp +++ b/Sources/ComputeCxx/Attribute/Node/Node.cpp @@ -8,15 +8,20 @@ namespace AG { -void Node::update_self(const Graph &graph, void *new_self) { - auto type = graph.attribute_type(_type_id); +void *Node::get_self(const AttributeType &type) const { void *self = ((char *)this + type.attribute_offset()); - if (has_indirect_self()) { + if (_flags.has_indirect_self()) { self = *(void **)self; } + return self; +} + +void Node::update_self(const Graph &graph, void *new_self) { + auto type = graph.attribute_type(_type_id); + void *self = get_self(type); - if (!_state.is_self_initialized()) { - _state = _state.with_self_initialized(true); + if (!state().is_self_initialized()) { + set_state(state().with_self_initialized(true)); type.self_metadata().vw_initializeWithCopy(static_cast(self), static_cast(new_self)); } else { @@ -26,21 +31,26 @@ void Node::update_self(const Graph &graph, void *new_self) { } void Node::destroy_self(const Graph &graph) { - if (!_state.is_self_initialized()) { + if (!state().is_self_initialized()) { return; } - _state = _state.with_self_initialized(false); + set_state(state().with_self_initialized(false)); auto type = graph.attribute_type(_type_id); - void *self = ((char *)this + type.attribute_offset()); - if (has_indirect_self()) { - self = *(void **)self; - } + void *self = get_self(type); - type.v_destroy_self(self); + type.vt_destroy_self(self); type.self_metadata().vw_destroy(static_cast(self)); } +void *Node::get_value() const { + void *value = _value.get(); + if (_flags.has_indirect_value()) { + value = *(void **)value; + } + return value; +} + void Node::allocate_value(Graph &graph, data::zone &zone) { if (_value) { return; @@ -50,7 +60,7 @@ void Node::allocate_value(Graph &graph, data::zone &zone) { size_t size = type.value_metadata().vw_size(); size_t alignment_mask = type.value_metadata().getValueWitnesses()->getAlignmentMask(); - if (has_indirect_value()) { + if (_flags.has_indirect_value()) { _value = zone.alloc_bytes_recycle(sizeof(void *), sizeof(void *) - 1); void *persistent_buffer = zone.alloc_persistent(size); *_value.unsafe_cast().get() = persistent_buffer; @@ -66,16 +76,13 @@ void Node::allocate_value(Graph &graph, data::zone &zone) { } void Node::destroy_value(Graph &graph) { - if (!_state.is_value_initialized()) { + if (!state().is_value_initialized()) { return; } - _state = _state.with_value_initialized(false); + set_state(state().with_value_initialized(false)); auto type = graph.attribute_type(_type_id); - void *value = _value.get(); - if (has_indirect_value()) { - value = *(void **)value; - } + void *value = get_value(); type.value_metadata().vw_destroy(static_cast(value)); } @@ -83,24 +90,18 @@ void Node::destroy_value(Graph &graph) { void Node::destroy(Graph &graph) { auto type = graph.attribute_type(_type_id); - if (_state.is_value_initialized()) { - void *value = _value.get(); - if (has_indirect_value()) { - value = *(void **)value; - } + if (state().is_value_initialized()) { + void *value = get_value(); type.value_metadata().vw_destroy(static_cast(value)); } if (_value) { graph.did_destroy_node_value(type.value_metadata().vw_size()); } - if (_state.is_self_initialized()) { - void *self = ((char *)this + type.attribute_offset()); - if (has_indirect_self()) { - self = *(void **)self; - } + if (state().is_self_initialized()) { + void *self = get_self(type); - type.v_destroy_self(self); + type.vt_destroy_self(self); type.self_metadata().vw_destroy(static_cast(self)); } } diff --git a/Sources/ComputeCxx/Attribute/Node/Node.h b/Sources/ComputeCxx/Attribute/Node/Node.h index dc2cb87..e7ab16b 100644 --- a/Sources/ComputeCxx/Attribute/Node/Node.h +++ b/Sources/ComputeCxx/Attribute/Node/Node.h @@ -3,6 +3,8 @@ #include #include "Data/Pointer.h" +#include "Data/Vector.h" +#include "Edge.h" CF_ASSUME_NONNULL_BEGIN @@ -15,56 +17,164 @@ class AttributeType; class Graph; class Node { - private: + public: class State { - public: + private: enum : uint8_t { - ValueInitialized = 1 << 4, - SelfInitialized = 1 << 5, + Dirty = 1 << 0, // 0x01 // Unknown0 = 1 << 0, + Pending = 1 << 1, // 0x02 // Unknown1 = 1 << 1, + MainThread = 1 << 2, // 0x04 set from attribute type flags & 8 + MainThreadOnly = 1 << 3, // 0x08 set from attribute type flags & 8 + + ValueInitialized = 1 << 4, // 0x10 + SelfInitialized = 1 << 5, // 0x20 + Updating = 1 << 6, // 0x40 // TODO: make this and next bit a count 0..<4 + UpdatingCyclic = 1 << 7, // 0x80 + }; - private: uint8_t _data; - explicit constexpr State(uint8_t data) : _data(data) {}; public: - bool is_value_initialized() { return _data & ValueInitialized; }; + explicit constexpr State(uint8_t data = 0) : _data(data) {}; + uint8_t data() const { return _data; } + + bool is_dirty() const { return _data & Dirty; } + State with_dirty(bool value) const { return State((_data & ~Dirty) | (value ? Dirty : 0)); }; + + bool is_pending() const { return _data & Pending; } + State with_pending(bool value) const { return State((_data & ~Pending) | (value ? Pending : 0)); }; + + bool is_main_thread() const { return _data & MainThread; } + State with_main_thread(bool value) const { return State((_data & ~MainThread) | (value ? MainThread : 0)); }; + + bool is_main_thread_only() const { return _data & MainThreadOnly; } + State with_main_thread_only(bool value) const { + return State((_data & ~MainThreadOnly) | (value ? MainThreadOnly : 0)); + }; + + bool is_value_initialized() const { return _data & ValueInitialized; }; State with_value_initialized(bool value) const { return State((_data & ~ValueInitialized) | (value ? ValueInitialized : 0)); }; - bool is_self_initialized() { return _data & SelfInitialized; }; + bool is_self_initialized() const { return _data & SelfInitialized; }; State with_self_initialized(bool value) const { return State((_data & ~SelfInitialized) | (value ? SelfInitialized : 0)); }; + + State with_updating(bool value) const { return State((_data & ~Updating) | (value ? Updating : 0)); }; + + bool is_updating() const { return _data & (Updating | UpdatingCyclic); } + bool is_updating_cyclic() const { return (_data & (Updating | UpdatingCyclic)) == (Updating | UpdatingCyclic); } + uint8_t update_count() const { return _data >> 6; } }; - enum Flags : uint8_t { - HasIndirectSelf = 1 << 0, - HasIndirectValue = 1 << 1, + class Flags { + private: + enum : uint8_t { + HasIndirectSelf = 1 << 0, // 0x01 + HasIndirectValue = 1 << 1, // 0x02 + + InputsTraverseContexts = 1 << 2, // 0x04 + InputsUnsorted = 1 << 3, // 0x08 + Cacheable = 1 << 4, // 0x10 + + Unknown0x20 = 1 << 5, // 0x20 - initial value // see Graph::update_main_refs + SelfModified = 1 << 6, // 0x40 + }; + uint8_t _data; + + public: + explicit constexpr Flags(uint8_t data = 0) : _data(data) {}; + uint8_t data() const { return _data; }; + + bool has_indirect_self() const { return _data & HasIndirectSelf; } + void set_has_indirect_self(bool value) { _data = (_data & ~HasIndirectSelf) | (value ? HasIndirectSelf : 0); }; + + bool has_indirect_value() const { return _data & HasIndirectValue; } + void set_has_indirect_value(bool value) { + _data = (_data & ~HasIndirectValue) | (value ? HasIndirectValue : 0); + }; + + bool inputs_traverse_contexts() const { return _data & InputsTraverseContexts; }; + void set_inputs_traverse_contexts(bool value) { + _data = (_data & ~InputsTraverseContexts) | (value ? InputsTraverseContexts : 0); + }; + + bool inputs_unsorted() const { return _data & InputsUnsorted; }; + void set_inputs_unsorted(bool value) { _data = (_data & ~InputsUnsorted) | (value ? InputsUnsorted : 0); }; + + bool cacheable() const { return _data & Cacheable; }; + void set_cacheable(bool value) { _data = (_data & ~Cacheable) | (value ? Cacheable : 0); }; + + bool unknown0x20() const { return _data & Unknown0x20; }; + void set_unknown0x20(bool value) { _data = (_data & ~Unknown0x20) | (value ? Unknown0x20 : 0); }; + + bool self_modified() const { return _data & SelfModified; }; + void set_self_modified(bool value) { _data = (_data & ~SelfModified) | (value ? SelfModified : 0); }; }; + private: State _state; - uint32_t _type_id; - uint8_t _field1; - uint8_t _field2; + unsigned int _type_id : 24; + RelativeAttributeID _relative_offset; + AGAttributeFlags _subgraph_flags; Flags _flags; data::ptr _value; + data::vector _inputs; + data::vector _outputs; + public: + Node(State state, uint32_t type_id, Flags flags) : _state(state), _type_id(type_id), _flags(flags) {}; + + const State &state() const { return _state; }; + void set_state(State state) { _state = state; }; + uint32_t type_id() const { return _type_id; }; - bool has_indirect_self() const { return _flags & Flags::HasIndirectSelf; }; + uint8_t value_state() const { + return (state().is_dirty() ? 1 : 0) << 0 | (state().is_pending() ? 1 : 0) << 1 | + (state().is_updating() ? 1 : 0) << 2 | (state().is_value_initialized() ? 1 : 0) << 3 | + (state().is_main_thread() ? 1 : 0) << 4 | (flags().unknown0x20() ? 1 : 0) << 5 | + (state().is_main_thread_only() ? 1 : 0) << 6 | (flags().self_modified() ? 1 : 0) << 7; + }; + + RelativeAttributeID relative_offset() const { return _relative_offset; }; + void set_relative_offset(RelativeAttributeID relative_offset) { _relative_offset = relative_offset; }; + + AGAttributeFlags &subgraph_flags() { return _subgraph_flags; }; + const AGAttributeFlags &subgraph_flags() const { return _subgraph_flags; }; + void set_subgraph_flags(AGAttributeFlags subgraph_flags) { _subgraph_flags = subgraph_flags; }; + + Flags &flags() { return _flags; }; + const Flags &flags() const { return _flags; }; + void set_flags(Flags flags) { _flags = flags; }; + + void sort_inputs_if_needed() { + if (_flags.inputs_unsorted()) { + _flags.set_inputs_unsorted(false); + std::sort(_inputs.begin(), _inputs.end()); + } + } + + void *get_self(const AttributeType &type) const; void update_self(const Graph &graph, void *new_self); void destroy_self(const Graph &graph); - bool has_indirect_value() const { return _flags * Flags::HasIndirectValue; }; + void *get_value() const; void allocate_value(Graph &graph, data::zone &zone); void destroy_value(Graph &graph); void destroy(Graph &graph); + + data::vector inputs() const { return _inputs; }; + data::vector outputs() const { return _outputs; }; }; +static_assert(sizeof(Node) == 0x1c); + } // namespace AG CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Attribute/WeakAttributeID.cpp b/Sources/ComputeCxx/Attribute/WeakAttributeID.cpp index 1d0c8e4..cf3ee4a 100644 --- a/Sources/ComputeCxx/Attribute/WeakAttributeID.cpp +++ b/Sources/ComputeCxx/Attribute/WeakAttributeID.cpp @@ -10,15 +10,15 @@ bool WeakAttributeID::expired() const { uint64_t raw_page_seed = data::table::shared().raw_page_seed(_attribute.page_ptr()); if (raw_page_seed & 0xff00000000) { auto zone_info = data::zone::info::from_raw_value(uint32_t(raw_page_seed)); - if (zone_info.zone_id() == _zone_id) { + if (zone_info.zone_id() == _subgraph_id) { return false; } } return true; } -const AttributeID &WeakAttributeID::attribute() const { - return _attribute; +const AttributeID &WeakAttributeID::evaluate() const { + return _attribute.has_value() && !expired() ? _attribute : AttributeIDNil; }; } // namespace AG diff --git a/Sources/ComputeCxx/Attribute/WeakAttributeID.h b/Sources/ComputeCxx/Attribute/WeakAttributeID.h index 3f3eabf..a2e4cdd 100644 --- a/Sources/ComputeCxx/Attribute/WeakAttributeID.h +++ b/Sources/ComputeCxx/Attribute/WeakAttributeID.h @@ -3,6 +3,7 @@ #include #include +#include "AGWeakAttribute.h" #include "AttributeID.h" CF_ASSUME_NONNULL_BEGIN @@ -12,11 +13,23 @@ namespace AG { class WeakAttributeID { private: AttributeID _attribute; - uint32_t _zone_id; + uint32_t _subgraph_id; public: + WeakAttributeID(AttributeID attribute, uint32_t subgraph_id) : _attribute(attribute), _subgraph_id(subgraph_id) {}; + + AGWeakAttribute to_cf() const { return AGWeakAttribute(_attribute, _subgraph_id); }; + static WeakAttributeID from_cf(AGWeakAttribute data) { + return WeakAttributeID(AttributeID::from_storage(data.attribute), data.subgraph_id); + }; + + const AttributeID &attribute() const { return _attribute; }; + uint32_t subgraph_id() const { return _subgraph_id; }; + bool expired() const; - const AttributeID &attribute() const; + + /// Returns the attribute it is has not expired, otherwise returns the nil attribute. + const AttributeID &evaluate() const; }; } // namespace AG diff --git a/Sources/ComputeCxx/Closure/AGClosure.cpp b/Sources/ComputeCxx/Closure/AGClosure.cpp new file mode 100644 index 0000000..c1876bb --- /dev/null +++ b/Sources/ComputeCxx/Closure/AGClosure.cpp @@ -0,0 +1,7 @@ +#include "AGClosure.h" + +#include + +void AGRetainClosure(AGClosureStorage *closure) { ::swift::swift_retain((::swift::HeapObject *)closure->context); } + +void AGReleaseClosure(AGClosureStorage *closure) { ::swift::swift_release((::swift::HeapObject *)closure->context); } diff --git a/Sources/ComputeCxx/Closure/AGClosure.h b/Sources/ComputeCxx/Closure/AGClosure.h new file mode 100644 index 0000000..f63588b --- /dev/null +++ b/Sources/ComputeCxx/Closure/AGClosure.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "AGSwiftSupport.h" + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +struct AGOpaqueValue; + +class AGClosureStorage { + public: + const AGOpaqueValue *_Nullable function; + const AGOpaqueValue *_Nullable context; +} SWIFT_SHARED_REFERENCE(AGRetainClosure, AGReleaseClosure); + +typedef struct AGClosureStorage *AGClosureRef AG_SWIFT_NAME(Closure); + +void AGRetainClosure(AGClosureRef closure); +void AGReleaseClosure(AGClosureRef closure); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Closure/ClosureFunction.h b/Sources/ComputeCxx/Closure/ClosureFunction.h index f3ac13e..6f22698 100644 --- a/Sources/ComputeCxx/Closure/ClosureFunction.h +++ b/Sources/ComputeCxx/Closure/ClosureFunction.h @@ -31,4 +31,28 @@ template class ClosureFunction { } }; +template + requires std::is_pointer_v +using ClosureFunctionVP = ClosureFunction; + +template + requires std::same_as +using ClosureFunctionVV = ClosureFunction; + +template + requires std::unsigned_integral +using ClosureFunctionCI = ClosureFunction; + +template + requires std::same_as +using ClosureFunctionPV = ClosureFunction; + +template + requires std::same_as && std::unsigned_integral +using ClosureFunctionAV = ClosureFunction; + +template + requires std::same_as && std::unsigned_integral +using ClosureFunctionAB = ClosureFunction; + } // namespace AG diff --git a/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp b/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp index eec1f10..0368871 100644 --- a/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp +++ b/Sources/ComputeCxx/Comparison/LayoutDescriptor.cpp @@ -6,8 +6,11 @@ #include "AGComparison.h" #include "Builder.h" #include "Compare.h" +#include "Graph/Graph.h" +#include "Graph/UpdateStack.h" #include "Swift/Metadata.h" #include "Time/Time.h" +#include "Trace/Trace.h" #include "Utilities/HashTable.h" #include "ValueLayout.h" @@ -300,11 +303,13 @@ ValueLayout make_layout(const swift::metadata &type, AGComparisonMode default_mo AGComparisonMode equtable_minimum_mode = type.getValueWitnesses()->isPOD() ? AGComparisonModeEquatableAlways : AGComparisonModeEquatableUnlessPOD; if (equtable_minimum_mode <= builder.current_comparison_mode()) { + if (auto equatable = type.equatable()) { size_t offset = builder.current_offset(); size_t size = type.vw_size(); Builder::EqualsItem item = {offset, size, &type, equatable}; builder.get_items().push_back(item); + return builder.commit(type); } } @@ -327,9 +332,11 @@ size_t length(ValueLayout layout) { reader.skip(sizeof(void *)); reader.skip(sizeof(void *)); continue; + case ValueLayoutEntryKind::Indirect: reader.skip(sizeof(void *)); reader.skip(sizeof(void *)); + continue; case ValueLayoutEntryKind::Existential: reader.skip(sizeof(void *)); @@ -406,13 +413,13 @@ bool compare_bytes_top_level(const unsigned char *lhs, const unsigned char *rhs, size_t failure_location = 0; bool result = compare_bytes(lhs, rhs, size, &failure_location); if ((options & AGComparisonOptionsReportFailures) && !result) { - // for (auto update = Graph::current_update(); update != nullptr; update = update.get()->previous()) { - // auto graph = update.get()->graph(); - // auto attribute = update.get()->frames().back().attribute; - // graph->foreach_trace([&attribute, &lhs, &rhs, &failure_location](Trace &trace) { - // trace.compare_failed(attribute, lhs, rhs, failure_location, 1, nullptr); - // }); - // } + for (auto update = Graph::current_update(); update != nullptr; update = update.get()->previous()) { + auto graph = update.get()->graph(); + auto attribute = update.get()->frames().back().attribute; + graph->foreach_trace([&attribute, &lhs, &rhs, &failure_location](Trace &trace) { + trace.compare_failed(attribute, lhs, rhs, failure_location, 1, nullptr); + }); + } } return result; } @@ -465,12 +472,14 @@ bool compare_heap_objects(const unsigned char *lhs, const unsigned char *rhs, AG return false; } + // TODO: should this be cast to a HeapObject struct? auto lhs_type = (const swift::metadata *)lhs; auto rhs_type = (const swift::metadata *)rhs; if (lhs_type != rhs_type) { return false; } + // only place where heap mode is set to non-zero? HeapMode heap_mode = is_function ? HeapMode::CaptureRef : HeapMode::Option1; AGComparisonOptions fetch_options = options & AGComparisonOptionsComparisonModeMask; // this has the effect of allowing async fetch @@ -596,6 +605,7 @@ bool compare_partial(ValueLayout layout, const unsigned char *lhs, const unsigne Compare compare_object = Compare(); return compare_object(partial.layout, lhs, rhs, partial.location, remaining_size, options); } + // TODO: no return here? } return compare_bytes_top_level(lhs, rhs, size, options); @@ -693,6 +703,7 @@ Partial find_partial(ValueLayout layout, size_t range_location, size_t range_siz } continue; } + case ValueLayoutEntryKind::EnumStartVariadic: case ValueLayoutEntryKind::EnumStart0: case ValueLayoutEntryKind::EnumStart1: @@ -706,6 +717,7 @@ Partial find_partial(ValueLayout layout, size_t range_location, size_t range_siz reader.skip(length(reader.layout)); continue; } + case ValueLayoutEntryKind::EnumContinueVariadic: case ValueLayoutEntryKind::EnumContinue0: case ValueLayoutEntryKind::EnumContinue1: @@ -796,6 +808,7 @@ void print(std::string &output, ValueLayout layout) { print_format("(== #:size %d #:type %s)", type->vw_size(), type->name(false)); continue; } + case ValueLayoutEntryKind::Indirect: { auto type = reader.read_bytes(); reader.skip(sizeof(void *)); @@ -805,6 +818,7 @@ void print(std::string &output, ValueLayout layout) { print_format("(indirect #:size %d #:type %s)", type->vw_size(), type->name(false)); continue; } + case ValueLayoutEntryKind::Existential: { auto type = reader.read_bytes(); diff --git a/Sources/ComputeCxx/Data/Pointer.h b/Sources/ComputeCxx/Data/Pointer.h index 1757a1e..d6963f2 100644 --- a/Sources/ComputeCxx/Data/Pointer.h +++ b/Sources/ComputeCxx/Data/Pointer.h @@ -82,4 +82,14 @@ template class ptr { } // namespace data } // namespace AG +namespace std { + +// TODO: see if there's another way to synthesize this +template class hash> { + public: + std::uint64_t operator()(const AG::data::ptr &pointer) const { return pointer.offset(); } +}; + +} // namespace std + CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Data/Vector.h b/Sources/ComputeCxx/Data/Vector.h new file mode 100644 index 0000000..15dc88c --- /dev/null +++ b/Sources/ComputeCxx/Data/Vector.h @@ -0,0 +1,158 @@ +#pragma once + +#include + +#include "Pointer.h" +#include "Zone.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { +namespace data { + +template class vector { + public: + using value_type = T; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = value_type *_Nonnull; + using const_iterator = const value_type *_Nonnull; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using size_type = uint32_t; + + private: + struct Metadata { + // capacity can be calculated via 1 << capacity_exponent + unsigned int capacity_exponent : 5; + unsigned int size : 27; + }; + + Metadata _metadata; + ptr _data; + + void reserve_slow(zone *zone, size_type new_cap) { + + size_type new_capacity_exponent = 32 - std::countl_zero(new_cap - 1); + if (new_cap < 2) { + new_capacity_exponent = 1; + } + + size_type old_capacity = sizeof(T) * capacity(); + + // alignment_mask should be 3 for OutputEdge and 0 for InputEdge, i.e. don't insert padding + size_type alignment_mask = std::has_unique_object_representations_v ? alignof(T) - 1 : 0; + zone->realloc_bytes((ptr *)&_data, old_capacity, (size_type)sizeof(T) << new_capacity_exponent, + alignment_mask); + _metadata.capacity_exponent = new_capacity_exponent; + } + + public: + ~vector() { + for (auto i = 0; i < _metadata.size; ++i) { + _data.get()[i].~T(); + } + } + + // Element access + + reference operator[](size_type pos) { return _data.get()[pos]; }; + const_reference operator[](size_type pos) const { return _data.get()[pos]; }; + + reference front() { return *_data.get(); }; + const_reference front() const { return _data.get(); }; + + // Iterators + + iterator begin() { return _data.get(); }; + iterator end() { return _data.get() + _metadata.size; }; + const_iterator cbegin() const { return _data.get(); }; + const_iterator cend() const { return _data.get() + _metadata.size; }; + const_iterator begin() const { return cbegin(); }; + const_iterator end() const { return cend(); }; + + reverse_iterator rbegin() { return std::reverse_iterator(end()); }; + reverse_iterator rend() { return std::reverse_iterator(begin()); }; + const_reverse_iterator crbegin() const { return std::reverse_iterator(cend()); }; + const_reverse_iterator crend() const { return std::reverse_iterator(cbegin()); }; + const_reverse_iterator rbegin() const { return crbegin(); }; + const_reverse_iterator rend() const { return crend(); }; + + // Capacity + + bool empty() const { return _metadata.size == 0; }; + size_type size() const { return _metadata.size; }; + void reserve(zone *zone, size_type new_cap) { + if (new_cap <= capacity()) { + return; + } + reserve_slow(zone, new_cap); + } + size_type capacity() const { + if (_metadata.capacity_exponent == 0) { + return 0; + } + return 1 << _metadata.capacity_exponent; + }; + + // Modifiers + + // TODO: check order needs to be preserverd here, UpdateStack manages index... + + iterator insert(zone *zone, const_iterator pos, const T &value) { + reserve(zone, _metadata.size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (mutable_pos) value_type(value); + _metadata.size += 1; + return end(); + } + + iterator insert(zone *zone, const_iterator pos, T &&value) { + reserve(zone, _metadata.size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (pos) value_type(std::move(value)); + _metadata.size += 1; + return end(); + } + + iterator erase(iterator pos) { + if (pos == end()) { + return end(); + } + return erase(pos, pos + 1); + } + + iterator erase(iterator first, iterator last) { + auto count = last - first; + if (count == 0) { + return last; + } + for (auto iter = first; iter != last; iter++) { + iter->~T(); + } + for (auto iter = last, old_end = end(); iter != old_end; iter++) { + std::swap(*(iter - count), *iter); + } + _metadata.size -= count; + return end(); + } + + void push_back(zone *zone, const T &value) { + reserve(zone, _metadata.size + 1); + new (&_data.get()[_metadata.size]) value_type(value); + _metadata.size += 1; + } + + void push_back(zone *zone, T &&value) { + reserve(zone, _metadata.size + 1); + new (&_data.get()[_metadata.size]) value_type(std::move(value)); + _metadata.size += 1; + } +}; + +} // namespace data +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Data/Zone.cpp b/Sources/ComputeCxx/Data/Zone.cpp index 47feb63..e3d9fb0 100644 --- a/Sources/ComputeCxx/Data/Zone.cpp +++ b/Sources/ComputeCxx/Data/Zone.cpp @@ -104,6 +104,7 @@ ptr zone::alloc_slow(uint32_t size, uint32_t alignment_mask) { // check if we can use remaining bytes in this page ptr next_bytes = _first_page.advanced(_first_page->in_use); if (next_bytes.page_ptr() == _first_page) { + ptr aligned_next_bytes = next_bytes.aligned(); int32_t remaining_size = _first_page->total - _first_page->in_use + (next_bytes - aligned_next_bytes); if (remaining_size >= sizeof(bytes_info)) { diff --git a/Sources/ComputeCxx/Data/Zone.h b/Sources/ComputeCxx/Data/Zone.h index e8ea3a0..2e24f06 100644 --- a/Sources/ComputeCxx/Data/Zone.h +++ b/Sources/ComputeCxx/Data/Zone.h @@ -24,7 +24,7 @@ class zone { info(uint32_t value) : _value(value) {}; public: - uint32_t zone_id() { return _value & id_mask; }; + uint32_t zone_id() const { return _value & id_mask; }; // TODO: id() info with_zone_id(uint32_t zone_id) const { return info((_value & ~id_mask) | (zone_id & id_mask)); }; info with_deleted() const { return info(_value | deleted); }; @@ -61,7 +61,7 @@ class zone { // Paged memory ptr alloc_bytes(uint32_t size, uint32_t alignment_mask); ptr alloc_bytes_recycle(uint32_t size, uint32_t alignment_mask); - + // Persistent memory void *alloc_persistent(size_t size); diff --git a/Sources/ComputeCxx/Debug/AGDebugServer.cpp b/Sources/ComputeCxx/Debug/AGDebugServer.cpp new file mode 100644 index 0000000..ae15243 --- /dev/null +++ b/Sources/ComputeCxx/Debug/AGDebugServer.cpp @@ -0,0 +1,22 @@ +#include "AGDebugServer.h" + +#include "DebugServer.h" + +CFURLRef AGDebugServerCopyURL() { + if (AG::DebugServer::shared() != nullptr) { + return AG::DebugServer::shared()->copy_url(); + } + return nullptr; +} + +void AGDebugServerRun(uint32_t timeout) { + if (AG::DebugServer::shared() != nullptr) { + AG::DebugServer::shared()->run(timeout); + } +} + +void AGDebugServerStart(AGDebugServerRef server, uint32_t options) { + reinterpret_cast(server)->start(options); +} + +void AGDebugServerStop(AGDebugServerRef server) { reinterpret_cast(server)->stop(); } diff --git a/Sources/ComputeCxx/Debug/AGDebugServer.h b/Sources/ComputeCxx/Debug/AGDebugServer.h new file mode 100644 index 0000000..dc32a3f --- /dev/null +++ b/Sources/ComputeCxx/Debug/AGDebugServer.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "AGSwiftSupport.h" + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +typedef void *AGDebugServerRef AG_SWIFT_NAME(DebugServer); + +CFURLRef AGDebugServerCopyURL(); + +void AGDebugServerRun(uint32_t timeout); + +void AGDebugServerStart(AGDebugServerRef server, uint32_t options); + +void AGDebugServerStop(AGDebugServerRef server); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Debug/Connection.cpp b/Sources/ComputeCxx/Debug/Connection.cpp new file mode 100644 index 0000000..ce78a51 --- /dev/null +++ b/Sources/ComputeCxx/Debug/Connection.cpp @@ -0,0 +1,138 @@ +#include "DebugServer.h" + +#include + +namespace AG { + +DebugServer::Connection::Connection(DebugServer *server, int socket) { + _server = server; + _socket = socket; + + _dispatch_source = + util::objc_ptr(dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket, 0, dispatch_get_main_queue())); + dispatch_set_context(_dispatch_source.get(), this); + dispatch_source_set_event_handler_f(_dispatch_source.get(), handler); + dispatch_resume(_dispatch_source.get()); +} + +DebugServer::Connection::~Connection() { + dispatch_source_set_event_handler(_dispatch_source.get(), nullptr); + dispatch_set_context(_dispatch_source.get(), nullptr); + _dispatch_source = nullptr; + + close(_socket); +} + +namespace { + +bool blocking_read(int socket_fd, void *buffer, size_t size) { + if (size == 0) { + return true; + } + + size_t total_read = 0; + char *bytes = static_cast(buffer); + + while (total_read < size) { + ssize_t bytes_read = read(socket_fd, bytes + total_read, size - total_read); + + if (bytes_read > 0) { + total_read += bytes_read; + } else if (bytes_read == 0) { + // Socket closed + return false; + } else { + if (errno == EINTR) { + continue; // Interrupted, retry + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + // Non-blocking mode: No data available, retry or handle accordingly + continue; + } else { + perror("AGDebugServer: read"); + return false; + } + } + } + + return true; +} + +bool blocking_write(int socket_fd, const void *buffer, size_t size) { + if (size == 0) { + return true; + } + + size_t total_written = 0; + const char *bytes = static_cast(buffer); + + while (total_written < size) { + ssize_t bytes_written = write(socket_fd, bytes + total_written, size - total_written); + + if (bytes_written > 0) { + total_written += bytes_written; + } else if (bytes_written == 0) { + return false; // Unexpected write failure + } else { + if (errno == EINTR) { + continue; // Interrupted, retry + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + continue; // Non-blocking mode: Retry or handle accordingly + } else { + perror("AGDebugServer: write"); + return false; + } + } + } + + return true; +} + +} // namespace + +void DebugServer::Connection::handler(void *context) { + Connection *connection = (Connection *)context; + + uint32_t header[4]; + if (!blocking_read(connection->_socket, header, sizeof(header))) { + connection->_server->close_connection(connection); + return; + } + + if (header[0] != connection->_server->_token) { + connection->_server->close_connection(connection); + return; + } + + CFIndex length = header[2]; + CFMutableDataRef request_data = CFDataCreateMutable(kCFAllocatorDefault, length); + if (!request_data) { + connection->_server->close_connection(connection); + return; + } + + CFDataSetLength(request_data, length); + void *request_bytes = CFDataGetMutableBytePtr(request_data); + + if (blocking_read(connection->_socket, request_bytes, length)) { + CFDataRef inout_data = request_data; + DebugServer::receive(&inout_data); + if (inout_data) { + CFIndex response_length = CFDataGetLength(inout_data); + if (response_length >> 32 == 0) { + header[2] = (uint32_t)response_length; + if (blocking_write(connection->_socket, (const unsigned char *)header, sizeof(header))) { + const unsigned char *response_bytes = CFDataGetBytePtr(inout_data); + if (blocking_write(connection->_socket, response_bytes, response_length)) { + connection = nullptr; // do not close connection + } + } + } + } + } + + if (connection) { + connection->_server->close_connection(connection); + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Debug/DebugServer.h b/Sources/ComputeCxx/Debug/DebugServer.h new file mode 100644 index 0000000..e98d2d7 --- /dev/null +++ b/Sources/ComputeCxx/Debug/DebugServer.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include + +#include "Utilities/ObjCPointer.h" +#include "Vector/Vector.h" + +CF_ASSUME_NONNULL_BEGIN + +struct AGDebugServerMessageHeader {}; + +namespace AG { + +class DebugServer { + public: + enum Options : uint32_t { + + }; + + class Connection { + private: + DebugServer *_server; + int _socket; + util::objc_ptr _dispatch_source; + + public: + Connection(DebugServer *server, int socket); + ~Connection(); + + int socket() const { return _socket; }; + + static void handler(void *context); + }; + + private: + int _socket; + uint32_t _ip_address; + uint16_t _port; + uint16_t _padding; + uint32_t _token; + util::objc_ptr _dispatch_source; + vector, 0, uint64_t> _clients; + + static DebugServer *_shared_server; + + public: + static DebugServer *shared() { return _shared_server; } + + static void start(uint32_t options); + static void stop(); + + static void accept_handler(void *context); + static void receive(CFDataRef _Nonnull *_Nonnull body); + + DebugServer(uint32_t options); + ~DebugServer(); + + void run(uint32_t timeout); + void shutdown(); + + void close_connection(Connection *connection); + + CFURLRef copy_url(); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Debug/DebugServer.mm b/Sources/ComputeCxx/Debug/DebugServer.mm new file mode 100644 index 0000000..092beed --- /dev/null +++ b/Sources/ComputeCxx/Debug/DebugServer.mm @@ -0,0 +1,287 @@ +#include "DebugServer.h" + +#import + +#include +#include +#include +#include +#include +#include +#include + +#include "Graph/AGDescription.h" +#include "Graph/Graph.h" +#include "Log/Log.h" +#include "Utilities/FreeDeleter.h" + +namespace AG { + +constexpr int backlog = 5; + +DebugServer *DebugServer::_shared_server = nullptr; + +void DebugServer::start(uint32_t options) { + if (options & 1 && !_shared_server) { + if (true /* && os_variant_has_internal_diagnostics() */) { + _shared_server = new DebugServer(options); + } + } + return _shared_server; +} + +void DebugServer::stop() { + if (_shared_server) { + delete _shared_server; + _shared_server = nullptr; + } + return _shared_server; +} + +DebugServer::DebugServer(uint32_t options) { + _socket = -1; + _ip_address = 0; + _port = 0; + + _token = arc4random(); + _dispatch_source = nullptr; + + _socket = socket(AF_INET, SOCK_STREAM, 0); + if (_socket < 0) { + perror("AGDebugServer: socket"); + return; + } + + fcntl(_socket, F_SETFD, FD_CLOEXEC); + + int option_value = 1; + setsockopt(_socket, SOL_SOCKET, SO_NOSIGPIPE, &option_value, sizeof(option_value)); + + sockaddr_in address = {}; + address.sin_family = AF_INET; + address.sin_port = 0; + address.sin_addr.s_addr = (options & 2) ? INADDR_ANY : htonl(INADDR_LOOPBACK); + + if (bind(_socket, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("AGDebugServer: bind"); + shutdown(); + return; + } + + socklen_t length = sizeof(address); + if (getsockname(_socket, (struct sockaddr *)&address, &length) < 0) { + perror("AGDebugServer: getsockname"); + shutdown(); + return; + } + + _ip_address = ntohl(address.sin_addr.s_addr); + _port = ntohs(address.sin_port); + + if (options & 2) { + struct ifaddrs *ifaddr = nullptr; + if (!getifaddrs(&ifaddr)) { + for (auto *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; + if (ntohl(sa->sin_addr.s_addr) != INADDR_LOOPBACK) { + _ip_address = ntohl(sa->sin_addr.s_addr); + break; + } + } + } + freeifaddrs(ifaddr); + } + } + + if (listen(_socket, backlog) < 0) { + perror("AGDebugServer: listen"); + shutdown(); + return this; + } + + _dispatch_source = + util::objc_ptr(dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, _socket, 0, dispatch_get_main_queue())); + dispatch_set_context(_dispatch_source.get(), this); + dispatch_source_set_event_handler_f(_dispatch_source.get(), accept_handler); + dispatch_resume(_dispatch_source.get()); + + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &_ip_address, ip_str, sizeof(ip_str)); + + os_log(misc_log(), "debug server graph://%s:%d/?token=%u", ip_str, _port, _token); + fprintf(stdout, "debug server graph://%s:%d/?token=%u\n", ip_str, _port, _token); +} + +DebugServer::~DebugServer() { shutdown(); } + +void DebugServer::shutdown() { + if (auto dispatch_source = _dispatch_source) { + dispatch_source_set_event_handler(dispatch_source.get(), nullptr); + dispatch_set_context(dispatch_source.get(), nullptr); + _dispatch_source = nullptr; + } + if (_socket >= 0) { + close(_socket); + _socket = -1; + } +} + +void DebugServer::accept_handler(void *context) { + DebugServer *server = (DebugServer *)context; + + struct sockaddr address = {}; + socklen_t length = sizeof(address); + int connection_socket = accept(server->_socket, &address, &length); + if (connection_socket < 0) { + perror("AGDebugServer: accept"); + return; + } + + fcntl(server->_socket, F_SETFD, FD_CLOEXEC); + + server->_clients.push_back(std::make_unique(server, connection_socket)); +} + +void DebugServer::run(uint32_t timeout) { + fd_set write_fds; + struct timeval tv; + + bool accepted = false; + while (!accepted || !_clients.empty()) { + FD_ZERO(&write_fds); + FD_SET(_socket, &write_fds); + + int max_socket = _socket; + for (auto &connection : _clients) { + FD_SET(connection->socket(), &write_fds); + if (connection->socket() > max_socket) { + max_socket = connection->socket(); + } + } + + tv.tv_sec = timeout; + tv.tv_usec = 0; + + int activity = select(max_socket + 1, nullptr, &write_fds, nullptr, &tv); + if (activity <= 0) { + if (errno != EAGAIN) { + perror("AGDebugServer: select"); + return; + } + } else { + if (FD_ISSET(_socket, &write_fds)) { + accept_handler(this); + accepted = true; + } + + for (uint64_t i = 0; i < _clients.size(); ++i) { + Connection *connection = _clients[i].get(); + if (FD_ISSET(connection->socket(), &write_fds)) { + FD_CLR(connection->socket(), &write_fds); + connection->handler(connection); + + // Restart loop to handle possible mutations to clients + i = 0; + } + } + } + } +} + +void DebugServer::receive(CFDataRef *body) { + @autoreleasepool { + id body_json = [NSJSONSerialization JSONObjectWithData:(__bridge NSData *)*body options:0 error:nullptr]; + if (!body_json) { + body = nullptr; + return; + } + + if (![body_json isKindOfClass:[NSDictionary class]]) { + body = nullptr; + return; + } + NSDictionary *body_dict = (NSDictionary *)body_json; + + NSString *command = body_dict[@"command"]; + + if ([command isEqual:@"graph/description"]) { + NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:body_dict]; + options[AGDescriptionFormat] = @"graph/dict"; + + id desc = Graph::description(nullptr, (__bridge CFDictionaryRef)options); + if (!desc) { + *body = nullptr; + return; + } + + NSData *new_data = [NSJSONSerialization dataWithJSONObject:desc options:0 error:nil]; + + *body = (__bridge CFDataRef)new_data; + } else if ([command isEqual:@"profiler/start"]) { + id profiler_flags_json = body_dict[@"flags"]; + uint32_t profiler_flags = + ([profiler_flags_json isKindOfClass:[NSNumber class]] ? [profiler_flags_json unsignedIntValue] : 0) | 1; + Graph::all_start_profiling(profiler_flags); + } else if ([command isEqual:@"profiler/stop"]) { + Graph::all_stop_profiling(); + } else if ([command isEqual:@"profiler/reset"]) { + Graph::all_reset_profile(); + } else if ([command isEqual:@"profiler/mark"]) { + id name_json = body_dict[@"name"]; + if ([name_json isKindOfClass:[NSString class]]) { + Graph::all_mark_profile([name_json UTF8String]); + } + } else if ([command isEqual:@"tracing/start"]) { + id tracing_flags_json = body_dict[@"flags"]; + uint32_t tracing_flags = + ([tracing_flags_json isKindOfClass:[NSNumber class]] ? [tracing_flags_json unsignedIntValue] : 0) | 1; + + auto subsystems = std::span(); + auto subsystems_vector = vector, 0, uint64_t>(); + id trace_subsystems_json = body_dict[@"subsystems"]; + if ([trace_subsystems_json isKindOfClass:[NSArray class]]) { + for (id trace_subsystem_json in trace_subsystems_json) { + if ([trace_subsystem_json isKindOfClass:[NSString class]]) { + const char *str = [trace_subsystem_json UTF8String]; + subsystems_vector.push_back(std::unique_ptr(str)); + } + } + subsystems = std::span((const char **)subsystems_vector.data(), subsystems_vector.size()); + } + + Graph::all_start_tracing(tracing_flags, subsystems); + } else if ([command isEqual:@"tracing/stop"]) { + Graph::all_stop_tracing(); + } else if ([command isEqual:@"tracing/sync"]) { + AG::Graph::all_sync_tracing(); + } + + *body = nullptr; + return; + } +} + +void DebugServer::close_connection(Connection *connection) { + auto iter = std::remove_if(_clients.begin(), _clients.end(), + [&connection](auto &candidate) -> bool { return candidate.get() == connection; }); + _clients.erase(iter); +} + +CFURLRef DebugServer::copy_url() { + if (_socket < 0) { + return nullptr; + } + + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &_ip_address, ip_str, sizeof(ip_str)); + + char bytes[0x100]; + snprintf_l(bytes, 0x100, nullptr, "graph://%s:%d/?token=%u", ip_str, _port, _token); + + CFIndex length = strlen(bytes); + return CFURLCreateWithBytes(kCFAllocatorDefault, (const unsigned char *)bytes, length, kCFStringEncodingUTF8, + nullptr); +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Encoder/Encoder.cpp b/Sources/ComputeCxx/Encoder/Encoder.cpp new file mode 100644 index 0000000..94e3120 --- /dev/null +++ b/Sources/ComputeCxx/Encoder/Encoder.cpp @@ -0,0 +1,94 @@ +#include "Encoder.h" + +#include "Errors/Errors.h" + +namespace AG { + +Encoder::Encoder(Delegate *_Nullable delegate, uint64_t flush_interval) + : _delegate(delegate), _flush_interval(flush_interval) { + if (delegate == nullptr && flush_interval != 0) { + precondition_failure("need a delegate if flush interval is non-zero"); + } +} + +void Encoder::encode_varint(uint64_t value) { + uint64_t width = 0; + if (value < 0x80) { + if (_buffer.capacity() > _buffer.size()) { + _buffer.push_back((const char)value); + return; + } + width = 1; + } else { + width = (63 - std::countl_zero(value)) / 7; + } + + uint64_t index = _buffer.size(); + _buffer.resize(_buffer.size() + width); // TODO: how to resize without zeroing memory + + uint64_t remaining_value = value; + while (remaining_value) { + _buffer[index] = ((char)remaining_value & 0x7f) | (0x7f < remaining_value) << 7; + index += 1; + remaining_value = remaining_value >> 7; + }; +} + +void Encoder::encode_fixed64(uint64_t value) { + uint64_t *pointer = (uint64_t *)(_buffer.data() + _buffer.size()); + _buffer.resize(_buffer.size() + sizeof(uint64_t)); + *pointer = value; +} + +void Encoder::encode_data(void *data, size_t length) { + encode_varint(length); + if (length == 0) { + return; + } + void *pointer = (void *)(_buffer.data() + _buffer.size()); + _buffer.resize(_buffer.size() + length); + memcpy(pointer, data, length); +} + +void Encoder::begin_length_delimited() { + // Reserve one byte for the length and store the position + uint64_t old_length = _buffer.size(); + _buffer.resize(_buffer.size() + 1); + _sections.push_back(old_length); +} + +void Encoder::end_length_delimited() { + uint64_t index = _sections.back(); + _sections.pop_back(); + + uint64_t length = _buffer.size() - (index + 1); + if (length < 0x80) { + _buffer[index] = length; + } else { + // The length requires more than one byte + uint64_t width = (63 - std::countl_zero(length)) / 7; + _buffer.resize(_buffer.size() + width - 1); + + memmove((void *)(_buffer.data() + index + width), (void *)(_buffer.data() + index + 1), length); + + uint64_t remaining_value = length; + while (remaining_value) { + _buffer[index] = ((char)remaining_value & 0x7f) | (0x7f < remaining_value) << 7; + index += 1; + remaining_value = remaining_value >> 7; + }; + } + + if (_sections.empty() && _flush_interval != 0 && _flush_interval <= _buffer.size()) { + flush(); + } +} + +void Encoder::flush() { + if (!_buffer.empty() && _delegate) { + _delegate->flush_encoder(*this); + _buffer.resize(0); + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Encoder/Encoder.h b/Sources/ComputeCxx/Encoder/Encoder.h new file mode 100644 index 0000000..e3bcc86 --- /dev/null +++ b/Sources/ComputeCxx/Encoder/Encoder.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "Vector/Vector.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Encoder { + public: + struct Delegate { + virtual int flush_encoder(Encoder &encoder){}; + }; + + private: + Delegate *_Nullable _delegate; + uint64_t _flush_interval; + void *_field_0x10; + vector _buffer; + vector _sections; + + public: + Encoder(Delegate *_Nullable delegate, uint64_t flush_interval); + + const vector &buffer() const { return _buffer; }; + + void encode_varint(uint64_t value); + void encode_fixed64(uint64_t value); + void encode_data(void *data, size_t length); + + void begin_length_delimited(); + void end_length_delimited(); + + void flush(); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/External/ExternalTrace.cpp b/Sources/ComputeCxx/External/ExternalTrace.cpp new file mode 100644 index 0000000..edbf4fd --- /dev/null +++ b/Sources/ComputeCxx/External/ExternalTrace.cpp @@ -0,0 +1,312 @@ +#include "ExternalTrace.h" + +#include "Comparison/AGComparison-Private.h" +#include "Graph/Context.h" +#include "Graph/Graph.h" + +void ExternalTrace::begin_trace(const AG::Graph &graph) { + auto cf_graph = graph.main_context()->to_cf(); + if (auto callback = _interface->begin_trace) { + callback(_trace, cf_graph); + } +} + +void ExternalTrace::end_trace(const AG::Graph &graph) { + auto cf_graph = graph.main_context()->to_cf(); + if (auto callback = _interface->end_trace) { + callback(_trace, cf_graph); + } +} + +void ExternalTrace::begin_update(const AG::Subgraph &subgraph, uint32_t options) { + auto cf_subgraph = subgraph.to_cf(); + if (auto callback = _interface->begin_update_subgraph) { + callback(_trace, cf_subgraph); + } +} + +void ExternalTrace::end_update(const AG::Subgraph &subgraph) { + auto cf_subgraph = subgraph.to_cf(); + if (auto callback = _interface->end_update_subgraph) { + callback(_trace, cf_subgraph); + } +} + +void ExternalTrace::begin_update(const AG::Graph::UpdateStack &update_stack, AG::data::ptr node, + uint32_t options) { + if (auto callback = _interface->begin_update_stack) { + callback(_trace, AGAttribute(AG::AttributeID(node))); + } +} + +void ExternalTrace::end_update(const AG::Graph::UpdateStack &update_stack, AG::data::ptr node, + AG::Graph::UpdateStatus update_status) { + if (auto callback = _interface->end_update_stack) { + callback(_trace, update_status == AG::Graph::UpdateStatus::Changed); + } +} + +void ExternalTrace::begin_update(AG::data::ptr node) { + if (auto callback = _interface->begin_update_node) { + callback(_trace); + } +} + +void ExternalTrace::end_update(AG::data::ptr node, bool changed) { + if (auto callback = _interface->end_update_node) { + callback(_trace); + } +} + +void ExternalTrace::begin_update(const AG::Graph::Context &context) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->begin_update_context) { + callback(_trace, cf_context); + } +} + +void ExternalTrace::end_update(const AG::Graph::Context &context) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->end_update_context) { + callback(_trace, cf_context); + } +} + +void ExternalTrace::begin_invalidation(const AG::Graph::Context &context, AG::AttributeID attribute) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->begin_invalidation) { + callback(_trace, cf_context, AGAttribute(attribute)); + } +} + +void ExternalTrace::end_invalidation(const AG::Graph::Context &context, AG::AttributeID attribute) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->end_invalidation) { + callback(_trace, cf_context, AGAttribute(attribute)); + } +} + +void ExternalTrace::begin_modify(AG::data::ptr node) { + if (auto callback = _interface->begin_modify) { + callback(_trace); + } +} + +void ExternalTrace::end_modify(AG::data::ptr node) { + if (auto callback = _interface->end_modify) { + callback(_trace); + } +} + +void ExternalTrace::begin_event(AG::data::ptr node, uint32_t event_id) { + if (auto callback = _interface->begin_event) { + if (auto subgraph = AG::AttributeID(node).subgraph()) { + const char *event_name = subgraph->graph()->key_name(event_id); + callback(_trace, AGAttribute(AG::AttributeID(node)), event_name); + } + } +} + +void ExternalTrace::end_event(AG::data::ptr node, uint32_t event_id) { + if (auto callback = _interface->end_event) { + if (auto subgraph = AG::AttributeID(node).subgraph()) { + const char *event_name = subgraph->graph()->key_name(event_id); + callback(_trace, AGAttribute(AG::AttributeID(node)), event_name); + } + } +} + +void ExternalTrace::created(const AG::Graph::Context &context) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->created_context) { + callback(_trace, cf_context); + } +} + +void ExternalTrace::destroy(const AG::Graph::Context &context) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->destroy_context) { + callback(_trace, cf_context); + } +} + +void ExternalTrace::needs_update(const AG::Graph::Context &context) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->needs_update_context) { + callback(_trace, cf_context); + } +} + +void ExternalTrace::created(const AG::Subgraph &subgraph) { + auto cf_subgraph = subgraph.to_cf(); + if (auto callback = _interface->created_subgraph) { + callback(_trace, cf_subgraph); + } +} + +void ExternalTrace::invalidate(const AG::Subgraph &subgraph) { + auto cf_subgraph = subgraph.to_cf(); + if (auto callback = _interface->invalidate_subgraph) { + callback(_trace, cf_subgraph); + } +} + +void ExternalTrace::destroy(const AG::Subgraph &subgraph) { + // no method in binary +} + +void ExternalTrace::add_child(const AG::Subgraph &subgraph, const AG::Subgraph &child) { + auto cf_subgraph = subgraph.to_cf(); + auto cf_child = subgraph.to_cf(); + if (auto callback = _interface->add_child_subgraph) { + callback(_trace, cf_subgraph, cf_child); + } +} + +void ExternalTrace::remove_child(const AG::Subgraph &subgraph, const AG::Subgraph &child) { + auto cf_subgraph = subgraph.to_cf(); + auto cf_child = subgraph.to_cf(); + if (auto callback = _interface->remove_child_subgraph) { + callback(_trace, cf_subgraph, cf_child); + } +} + +void ExternalTrace::added(AG::data::ptr node) { + if (auto callback = _interface->added_node) { + callback(_trace); + } +} + +void ExternalTrace::add_edge(AG::data::ptr node, AG::AttributeID input, uint8_t input_edge_flags) { + if (auto callback = _interface->add_edge) { + callback(_trace); + } +} + +void ExternalTrace::remove_edge(AG::data::ptr node, uint32_t input_index) { + if (auto callback = _interface->remove_edge) { + if (AG::AttributeID(node).subgraph()) { + callback(_trace); + } + } +} + +void ExternalTrace::set_edge_pending(AG::data::ptr node, uint32_t input_index, bool pending) { + if (auto callback = _interface->set_edge_pending) { + if (AG::AttributeID(node).subgraph()) { + callback(_trace); + } + } +} + +void ExternalTrace::set_dirty(AG::data::ptr node, bool dirty) { + if (auto callback = _interface->set_dirty) { + callback(_trace); + } +} + +void ExternalTrace::set_pending(AG::data::ptr node, bool pending) { + if (auto callback = _interface->set_pending) { + callback(_trace); + } +} + +void ExternalTrace::set_value(AG::data::ptr node, const void *value) { + if (auto callback = _interface->set_value) { + callback(_trace); + } +} + +void ExternalTrace::mark_value(AG::data::ptr node) { + if (auto callback = _interface->mark_value) { + callback(_trace); + } +} + +void ExternalTrace::added(AG::data::ptr indirect_node) { + if (auto callback = _interface->added_indirect_node) { + callback(_trace, AGAttribute(AG::AttributeID(indirect_node))); // TODO: check sets kind + } +} + +void ExternalTrace::set_source(AG::data::ptr indirect_node, AG::AttributeID source) { + if (auto callback = _interface->set_source) { + callback(_trace, AGAttribute(AG::AttributeID(indirect_node))); // TODO: check sets kind + } +} + +void ExternalTrace::set_dependency(AG::data::ptr indirect_node, AG::AttributeID dependency) { + if (auto callback = _interface->set_dependency) { + callback(_trace, AGAttribute(AG::AttributeID(indirect_node))); // TODO: check sets kind + } +} + +void ExternalTrace::mark_profile(const AG::Graph &graph, uint32_t event_id) { + if (auto callback = _interface->mark_profile) { + const char *event_name = graph.key_name(event_id); + callback(_trace, event_name); + } +} + +// void ExternalTrace::destroy(const AG::Subgraph &subgraph) { +// auto cf_subgraph = subgraph.to_cf(); +// if (auto callback = _interface->destroy_subgraph) { +// callback(_trace, cf_subgraph); +// } +// } + +void ExternalTrace::custom_event(const AG::Graph::Context &context, const char *event_name, const void *value, + const AG::swift::metadata &type) { + if (_interface->options != 0) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->custom_event) { + callback(_trace, cf_context, event_name, value, AGTypeID(&type)); + } + } +} + +void ExternalTrace::named_event(const AG::Graph::Context &context, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6) { + if (_interface->options > 1) { + auto cf_context = context.to_cf(); + if (auto callback = _interface->named_event) { + callback(_trace, cf_context, event_id, num_event_args, event_args, data, arg6); + } + } +} + +bool ExternalTrace::named_event_enabled(uint32_t event_id) { + if (_interface->options < 2) { + return false; + } + if (auto callback = _interface->named_event_enabled) { + return callback(_trace); + } + return _interface->named_event != nullptr; +} + +void ExternalTrace::set_deadline(uint64_t deadline) { + if (_interface->options > 2) { + if (auto callback = _interface->set_deadline) { + callback(_trace); + } + } +} + +void ExternalTrace::passed_deadline() { + if (_interface->options > 2) { + if (auto callback = _interface->passed_deadline) { + callback(_trace); + } + } +} + +void ExternalTrace::compare_failed(AG::data::ptr node, const void *lhs, const void *rhs, size_t range_offset, + size_t range_size, const AG::swift::metadata *_Nullable type) { + if (_interface->options > 3) { + AGComparisonStateStorage storage = {lhs, rhs, range_offset, range_size, AGTypeID(&type)}; + if (auto callback = _interface->compare_failed) { + callback(_trace, AGAttribute(AG::AttributeID(node)), &storage); + } + } +} diff --git a/Sources/ComputeCxx/External/ExternalTrace.h b/Sources/ComputeCxx/External/ExternalTrace.h new file mode 100644 index 0000000..62698ef --- /dev/null +++ b/Sources/ComputeCxx/External/ExternalTrace.h @@ -0,0 +1,151 @@ +#pragma once + +#include + +#include "Attribute/AGAttribute.h" +#include "Comparison/AGComparison.h" +#include "Graph/Context.h" +#include "Subgraph/Subgraph.h" +#include "Swift/AGType.h" +#include "Trace/Trace.h" + +CF_ASSUME_NONNULL_BEGIN + +class ExternalTrace : public AG::Trace { + public: + struct Interface { + uint64_t options; + + void (*_Nullable begin_trace)(void *trace, AGGraphStorage *graph); + void (*_Nullable end_trace)(void *trace, AGGraphStorage *graph); + + void (*_Nullable begin_update_subgraph)(void *trace, AGSubgraphStorage *subgraph); + void (*_Nullable end_update_subgraph)(void *trace, AGSubgraphStorage *subgraph); + void (*_Nullable begin_update_stack)(void *trace, AGAttribute attribute); + void (*_Nullable end_update_stack)(void *trace, bool changed); + void (*_Nullable begin_update_node)(void *trace); + void (*_Nullable end_update_node)(void *trace); + void (*_Nullable begin_update_context)(void *trace, AGGraphStorage *graph); + void (*_Nullable end_update_context)(void *trace, AGGraphStorage *graph); + + void (*_Nullable begin_invalidation)(void *trace, AGGraphStorage *graph, AGAttribute attribute); + void (*_Nullable end_invalidation)(void *trace, AGGraphStorage *graph, AGAttribute attribute); + + void (*_Nullable begin_modify)(void *trace); + void (*_Nullable end_modify)(void *trace); + + void (*_Nullable begin_event)(void *trace, AGAttribute attribute, const char *event_name); + void (*_Nullable end_event)(void *trace, AGAttribute attribute, const char *event_name); + + void (*_Nullable created_context)(void *trace, AGGraphStorage *graph); + void (*_Nullable destroy_context)(void *trace, AGGraphStorage *graph); + void (*_Nullable needs_update_context)(void *trace, AGGraphStorage *graph); + + void (*_Nullable created_subgraph)(void *trace, AGSubgraphStorage *subgraph); + void (*_Nullable invalidate_subgraph)(void *trace, AGSubgraphStorage *subgraph); + void (*_Nullable add_child_subgraph)(void *trace, AGSubgraphStorage *subgraph, AGSubgraphStorage *child); + void (*_Nullable remove_child_subgraph)(void *trace, AGSubgraphStorage *subgraph, AGSubgraphStorage *child); + + void (*_Nullable added_node)(void *trace); + void (*_Nullable add_edge)(void *trace); + void (*_Nullable remove_edge)(void *trace); + void (*_Nullable set_edge_pending)(void *trace); + + void (*_Nullable set_dirty)(void *trace); + void (*_Nullable set_pending)(void *trace); + void (*_Nullable set_value)(void *trace); + void (*_Nullable mark_value)(void *trace); + + void (*_Nullable added_indirect_node)(void *trace, AGAttribute attribute); + void (*_Nullable set_source)(void *trace, AGAttribute attribute); + void (*_Nullable set_dependency)(void *trace, AGAttribute attribute); + + void (*_Nullable mark_profile)(void *trace, const char *event_name); + + void (*_Nullable custom_event)(void *trace, AGGraphStorage *graph, const char *event_name, const void *value, + AGTypeID type); + void (*_Nullable named_event)(void *trace, AGGraphStorage *graph, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6); + bool (*_Nullable named_event_enabled)(void *trace); + void (*_Nullable set_deadline)(void *trace); + void (*_Nullable passed_deadline)(void *trace); + + void (*_Nullable compare_failed)(void *trace, AGAttribute attribute, AGComparisonState comparison_state); + }; + + private: + Interface *_interface; + void *_trace; + + public: + ExternalTrace(Interface *interface, void *trace) : _interface(interface), _trace(trace) {}; + ExternalTrace(uint64_t trace_id, Interface *interface, void *trace) : _interface(interface), _trace(trace) { + _trace_id = trace_id; + }; + + void begin_trace(const AG::Graph &graph); + void end_trace(const AG::Graph &graph); + + void begin_update(const AG::Subgraph &subgraph, uint32_t options); + void end_update(const AG::Subgraph &subgraph); + + void begin_update(const AG::Graph::UpdateStack &update_stack, AG::data::ptr node, uint32_t options); + void end_update(const AG::Graph::UpdateStack &update_stack, AG::data::ptr node, + AG::Graph::UpdateStatus update_status); + void begin_update(AG::data::ptr node); + void end_update(AG::data::ptr node, bool changed); + void begin_update(const AG::Graph::Context &context); + void end_update(const AG::Graph::Context &context); + + void begin_invalidation(const AG::Graph::Context &context, AG::AttributeID attribute); + void end_invalidation(const AG::Graph::Context &context, AG::AttributeID attribute); + + void begin_modify(AG::data::ptr node); + void end_modify(AG::data::ptr node); + + void begin_event(AG::data::ptr node, uint32_t event_id); + void end_event(AG::data::ptr node, uint32_t event_id); + + void created(const AG::Graph::Context &context); + void destroy(const AG::Graph::Context &context); + void needs_update(const AG::Graph::Context &context); + + void created(const AG::Subgraph &subgraph); + void invalidate(const AG::Subgraph &subgraph); + void destroy(const AG::Subgraph &subgraph); + + void add_child(const AG::Subgraph &subgraph, const AG::Subgraph &child); + void remove_child(const AG::Subgraph &subgraph, const AG::Subgraph &child); + + void added(AG::data::ptr node); + + void add_edge(AG::data::ptr node, AG::AttributeID input, uint8_t input_edge_flags); + void remove_edge(AG::data::ptr node, uint32_t input_index); + void set_edge_pending(AG::data::ptr node, uint32_t input_index, bool pending); + + void set_dirty(AG::data::ptr node, bool dirty); + void set_pending(AG::data::ptr node, bool pending); + void set_value(AG::data::ptr node, const void *value); + void mark_value(AG::data::ptr node); + + void added(AG::data::ptr indirect_node); + + void set_source(AG::data::ptr indirect_node, AG::AttributeID source); + void set_dependency(AG::data::ptr indirect_node, AG::AttributeID dependency); + + void mark_profile(const AG::Graph &graph, uint32_t event_id); + + void custom_event(const AG::Graph::Context &context, const char *event_name, const void *value, + const AG::swift::metadata &type); + void named_event(const AG::Graph::Context &context, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6); // TODO: what are these args? + bool named_event_enabled(uint32_t event_id); + + void set_deadline(uint64_t deadline); + void passed_deadline(); + + void compare_failed(AG::data::ptr node, const void *lhs, const void *rhs, size_t range_offset, + size_t range_size, const AG::swift::metadata *_Nullable type); +}; + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/AGDescription.h b/Sources/ComputeCxx/Graph/AGDescription.h new file mode 100644 index 0000000..c9d96ac --- /dev/null +++ b/Sources/ComputeCxx/Graph/AGDescription.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +NS_ASSUME_NONNULL_BEGIN + +extern NSString *AGDescriptionFormat; +extern NSString *AGDescriptionMaxFrames; +extern NSString *AGDescriptionIncludeValues; +extern NSString *AGDescriptionTruncationLimit; + +NS_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/AGGraph-Private.h b/Sources/ComputeCxx/Graph/AGGraph-Private.h new file mode 100644 index 0000000..321e2de --- /dev/null +++ b/Sources/ComputeCxx/Graph/AGGraph-Private.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "AGGraph.h" +#include "Graph/Context.h" +#include "Private/CFRuntime.h" + +CF_ASSUME_NONNULL_BEGIN + +struct AGGraphStorage { + CFRuntimeBase base; + AG::Graph::Context context; +}; + +struct AGGraphContextStorage { + AG::Graph::Context context; +}; + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/AGGraph.cpp b/Sources/ComputeCxx/Graph/AGGraph.cpp new file mode 100644 index 0000000..a1ec960 --- /dev/null +++ b/Sources/ComputeCxx/Graph/AGGraph.cpp @@ -0,0 +1,1148 @@ +#include "AGGraph-Private.h" + +#include + +#include "Attribute/AttributeID.h" +#include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" +#include "Attribute/OffsetAttributeID.h" +#include "Attribute/WeakAttributeID.h" +#include "Context.h" +#include "External/ExternalTrace.h" +#include "Graph.h" +#include "Private/CFRuntime.h" +#include "Trace/Trace.h" +#include "UpdateStack.h" + +namespace { + +CFRuntimeClass &graph_type_id() { + static auto finalize = [](CFTypeRef graph_ref) { + AGGraphStorage *storage = (AGGraphRef)graph_ref; + if (!storage->context.invalidated()) { + storage->context.AG::Graph::Context::~Context(); + } + }; + static CFRuntimeClass klass = { + 0, // version + "AGGraph", // className + NULL, // init + NULL, // copy, + finalize, + NULL, // equal + NULL, // hash + NULL, // copyFormattingDesc + NULL, // copyDebugDesc, + 0 // ?? + }; + return klass; +} + +} // namespace + +CFTypeID AGGraphGetTypeID() { + static CFTypeID type = _CFRuntimeRegisterClass(&graph_type_id()); + return type; +} + +AGGraphRef AGGraphCreate() { return AGGraphCreateShared(nullptr); }; + +AGGraphRef AGGraphCreateShared(AGGraphRef original) { + CFIndex extra_bytes = sizeof(struct AGGraphStorage) - sizeof(CFRuntimeBase); + struct AGGraphStorage *instance = + (struct AGGraphStorage *)_CFRuntimeCreateInstance(kCFAllocatorDefault, AGGraphGetTypeID(), extra_bytes, NULL); + if (!instance) { + AG::precondition_failure("memory allocation failure."); + } + + AG::Graph *graph; + if (original) { + if (original->context.invalidated()) { + AG::precondition_failure("invalidated graph"); + } + graph = &original->context.graph(); + AG::Graph::will_add_to_context(graph); + } else { + graph = new AG::Graph(); // ref = 1 + } + + new (&instance->context) AG::Graph::Context(graph); // ref + 1 + + AG::Graph::did_remove_from_context(graph); // ref - 1 + + instance->context.set_invalidated(false); + + return instance; +}; + +AGUnownedGraphRef AGGraphGetGraphContext(AGGraphRef graph) { + AG::Graph::Context *graph_context = AG::Graph::Context::from_cf(graph); + AG::Graph *unowned_graph = &graph_context->graph(); + fprintf(stdout, "AGGraphGetGraphContext %p -> %p", graph, unowned_graph); + return reinterpret_cast(unowned_graph); +} + +AGGraphRef AGGraphContextGetGraph(AGUnownedGraphContextRef storage) { + auto graph_context = reinterpret_cast(storage); + fprintf(stdout, "AGGraphContextGetGraph %p -> %p", storage, graph_context->to_cf()); + return graph_context->to_cf(); +} + +const void *AGGraphGetContext(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + return context->context_info(); +} + +void AGGraphSetContext(AGGraphRef graph, const void *context_info) { + auto context = AG::Graph::Context::from_cf(graph); + context->set_context_info(context_info); +} + +#pragma mark - Counters + +uint64_t AGGraphGetCounter(AGGraphRef graph, AGGraphCounterQuery query) { + auto context = AG::Graph::Context::from_cf(graph); + switch (query) { + case AGGraphCounterQueryNodeCount: + return context->graph().num_nodes(); + case AGGraphCounterQueryTransactionCount: + return context->graph().transaction_count(); + case AGGraphCounterQueryUpdateCount: + return context->graph().update_count(); + case AGGraphCounterQueryChangeCount: + return context->graph().change_count(); + case AGGraphCounterQueryContextID: + return context->unique_id(); + case AGGraphCounterQueryGraphID: + return context->graph().unique_id(); + case AGGraphCounterQueryContextThreadUpdating: + return context->thread_is_updating(); + case AGGraphCounterQueryThreadUpdating: + return context->graph().thread_is_updating(); + case AGGraphCounterQueryContextNeedsUpdate: + return context->needs_update(); + case AGGraphCounterQueryNeedsUpdate: + return context->graph().needs_update(); + case AGGraphCounterQueryMainThreadUpdateCount: + return context->graph().update_on_main_count(); + case AGGraphCounterQueryNodeTotalCount: + return context->graph().num_nodes_created(); + case AGGraphCounterQuerySubgraphCount: + return context->graph().num_subgraphs(); + case AGGraphCounterQuerySubgraphTotalCount: + return context->graph().num_subgraphs_created(); + default: + return 0; + } +} + +#pragma mark - Subgraph + +bool AGGraphBeginDeferringSubgraphInvalidation(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + bool old_value = context->graph().is_deferring_subgraph_invalidation(); + context->graph().set_deferring_subgraph_invalidation(true); + return old_value; +} + +void AGGraphEndDeferringSubgraphInvalidation(AGGraphRef graph, bool was_deferring_subgraph_invalidation) { + auto context = AG::Graph::Context::from_cf(graph); + if (!was_deferring_subgraph_invalidation) { + context->graph().set_deferring_subgraph_invalidation(false); + context->graph().invalidate_subgraphs(); + } +} + +#pragma mark - Attribute types + +uint32_t +AGGraphInternAttributeType(AGUnownedGraphRef unowned_graph, AGTypeID type, + const void *(*intern_function)(const void *context AG_SWIFT_CONTEXT)AG_SWIFT_CC(swift), + const void *intern_function_context) { + auto metadata = reinterpret_cast(type); + AG::Graph *graph = reinterpret_cast(unowned_graph); + return graph->intern_type(metadata, AG::ClosureFunctionVP(intern_function, intern_function_context)); +} + +void AGGraphVerifyType(AGAttribute attribute, AGTypeID type) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + if (attribute_id.is_direct()) { + auto metadata = reinterpret_cast(type); + auto attribute_type = subgraph->graph()->attribute_type(attribute_id.to_node().type_id()); + if (&attribute_type.value_metadata() != metadata) { + AG::precondition_failure("type check failed: %u, expected %s, got %s", attribute, metadata->name(false), + attribute_type.value_metadata().name(false)); + } + } +} + +#pragma mark - Attributes + +AGAttribute AGGraphCreateAttribute(uint32_t type_id, const void *body, const void *_Nullable value) { + auto current_subgraph = AG::Subgraph::current_subgraph(); + if (!current_subgraph) { + AG::precondition_failure("no subgraph active while adding attribute"); + } + auto node = current_subgraph->graph()->add_attribute(*current_subgraph, type_id, body, value); + return AGAttribute(AG::AttributeID(node)); +} + +AGGraphRef AGGraphGetAttributeGraph(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + if (auto subgraph = attribute_id.subgraph()) { + auto context_id = subgraph->context_id(); + if (context_id != 0) { + if (auto context = subgraph->graph()->context_with_id(context_id)) { + return context->to_cf(); + } + } + } + AG::precondition_failure("no graph: %u", attribute); +} + +AGAttributeInfo AGGraphGetAttributeInfo(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + const void *body = nullptr; + const AG::AttributeType *type = subgraph->graph()->attribute_ref(attribute_id.to_ptr(), &body); + return AGAttributeInfo(reinterpret_cast(type), body); +} + +AGAttributeFlags AGGraphGetFlags(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + return attribute_id.to_node().subgraph_flags(); +} + +void AGGraphSetFlags(AGAttribute attribute, AGAttributeFlags flags) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.subgraph()->set_flags(attribute_id.to_ptr(), flags); +} + +void AGGraphMutateAttribute(AGAttribute attribute, AGTypeID type, bool invalidating, + void (*modify)(const void *context AG_SWIFT_CONTEXT, void *body) AG_SWIFT_CC(swift), + const void *context) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + subgraph->graph()->attribute_modify(attribute_id.to_ptr(), + *reinterpret_cast(type), + AG::ClosureFunctionPV(modify, context), invalidating); +} + +bool AGGraphSearch(AGAttribute attribute, AGSearchOptions options, + bool (*predicate)(const void *context AG_SWIFT_CONTEXT, AGAttribute attribute) AG_SWIFT_CC(swift), + const void *context) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + return subgraph->graph()->breadth_first_search(attribute_id, AG::Graph::SearchOptions(options), + AG::ClosureFunctionAB(predicate, context)); +} + +#pragma mark - Cached attributes + +namespace { + +void *read_cached_attribute(uint64_t identifier, const AG::swift::metadata &metadata, void *body, + const AG::swift::metadata &value_type, AGCachedValueOptions options, + AG::AttributeID attribute, uint8_t *state_out, + AG::ClosureFunctionCI closure) { + + auto current_update = AG::Graph::current_update(); + AG::Graph::UpdateStack *stack = current_update.tag() == 0 ? current_update.get() : nullptr; + + AG::Subgraph *subgraph = nullptr; + if (attribute.has_value()) { + attribute.validate_data_offset(); + subgraph = attribute.subgraph(); + } else { + if (stack != nullptr) { + subgraph = AG::AttributeID(stack->frames().back().attribute).subgraph(); + } else { + subgraph = AG::Subgraph::current_subgraph(); + } + } + if (subgraph == nullptr) { + AG::precondition_failure("no subgraph"); + } + + AG::data::ptr cached = subgraph->cache_fetch(identifier, metadata, body, closure); + if (cached == nullptr) { + return nullptr; + } + + if (stack == nullptr) { + void *value = subgraph->graph()->value_ref(AG::AttributeID(cached), 0, value_type, state_out); + subgraph->cache_insert(cached); + return value; + } + + uint8_t input_flags = (options & 1) == 0 ? 0 : 1; + return subgraph->graph()->input_value_ref(stack->frames().back().attribute, AG::AttributeID(cached), 0, input_flags, + value_type, state_out); +} + +} // namespace + +void *AGGraphReadCachedAttribute(uint64_t identifier, AGTypeID type, void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute attribute, bool *changed_out, + uint32_t (*closure)(const void *context AG_SWIFT_CONTEXT, + AGUnownedGraphRef graph) AG_SWIFT_CC(swift), + const void *closure_context) { + auto metadata = reinterpret_cast(type); + auto value_metadata = reinterpret_cast(value_type); + + uint8_t state = 0; + void *value = read_cached_attribute( + identifier, *metadata, body, *value_metadata, options, AG::AttributeID::from_storage(attribute), &state, + AG::ClosureFunctionCI(closure, closure_context)); + if (changed_out) { + *changed_out = state & 1 ? true : false; + } + return value; +} + +void *AGGraphReadCachedAttributeIfExists(uint64_t identifier, AGTypeID type, void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute attribute, bool *changed_out) { + auto metadata = reinterpret_cast(type); + auto value_metadata = reinterpret_cast(value_type); + + uint8_t state = 0; + void *value = read_cached_attribute(identifier, *metadata, body, *value_metadata, options, + AG::AttributeID::from_storage(attribute), &state, nullptr); + if (changed_out) { + *changed_out = state & 1 ? true : false; + } + return value; +} + +#pragma mark - Current attribute + +AGAttribute AGGraphGetCurrentAttribute() { + auto update = AG::Graph::current_update(); + if (update.tag() == 0) { + if (auto update_stack = update.get()) { + auto frame = update_stack->frames().back(); + if (frame.attribute) { + return AGAttribute(AG::AttributeID(frame.attribute)); + } + } + } + return AGAttributeNil; +} + +bool AGGraphCurrentAttributeWasModified() { + auto update = AG::Graph::current_update(); + if (update.tag() == 0) { + if (auto update_stack = update.get()) { + auto frame = update_stack->frames().back(); + if (frame.attribute) { + return frame.attribute->flags().self_modified(); + } + } + } + return false; +} + +#pragma mark - Indirect attributes + +namespace { + +AG::AttributeID create_indirect_attribute(AG::AttributeID attribute_id, std::optional size) { + auto current_subgraph = AG::Subgraph::current_subgraph(); + if (current_subgraph == nullptr) { + AG::precondition_failure("no subgraph active while making indirection"); + } + + AG::data::ptr indirect_node = + current_subgraph->graph()->add_indirect_attribute(*current_subgraph, attribute_id, 0, size, true); + return AG::AttributeID(indirect_node); // TODO: check adds kind +} + +} // namespace + +AGAttribute AGGraphCreateIndirectAttribute(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + return create_indirect_attribute(attribute_id, std::optional()); +} + +AGAttribute AGGraphCreateIndirectAttribute2(AGAttribute attribute, size_t size) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + return create_indirect_attribute(attribute_id, std::optional(size)); +} + +AGAttribute AGGraphGetIndirectAttribute(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_indirect()) { + return attribute_id; + } + return attribute_id.to_indirect_node().source().attribute(); +} + +void AGGraphSetIndirectAttribute(AGAttribute attribute, AGAttribute source) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_indirect() || attribute_id.subgraph() == nullptr) { + AG::precondition_failure("invalid indirect attribute: %u", attribute); + } + + auto source_id = AG::AttributeID::from_storage(source); + if (source_id.is_nil()) { + attribute_id.subgraph()->graph()->indirect_attribute_reset(attribute_id.to_ptr(), false); + } else { + attribute_id.subgraph()->graph()->indirect_attribute_set(attribute_id.to_ptr(), source_id); + } +} + +void AGGraphResetIndirectAttribute(AGAttribute attribute, bool non_nil) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_indirect() || attribute_id.subgraph() == nullptr) { + AG::precondition_failure("invalid indirect attribute: %u", attribute); + } + + attribute_id.subgraph()->graph()->indirect_attribute_reset(attribute_id.to_ptr(), non_nil); +} + +AGAttribute AGGraphGetIndirectDependency(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_indirect() || attribute_id.subgraph() == nullptr) { + AG::precondition_failure("invalid indirect attribute: %u", attribute); + } + + return attribute_id.subgraph()->graph()->indirect_attribute_dependency(attribute_id.to_ptr()); +} + +void AGGraphSetIndirectDependency(AGAttribute attribute, AGAttribute dependency) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_indirect() || attribute_id.subgraph() == nullptr) { + AG::precondition_failure("invalid indirect attribute: %u", attribute); + } + + return attribute_id.subgraph()->graph()->indirect_attribute_set_dependency( + attribute_id.to_ptr(), AG::AttributeID::from_storage(dependency)); +} + +void AGGraphRegisterDependency(AGAttribute dependency, uint8_t input_edge_flags) { + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() != 0 || update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + auto update_stack = update_stack_ptr.get(); + auto graph = update_stack->graph(); + auto frame = update_stack->frames().back(); + + graph->input_value_add(frame.attribute, AG::AttributeID::from_storage(dependency), input_edge_flags); +} + +#pragma mark - Offset attributes + +namespace { + +AG::AttributeID create_offset_attribute(AG::AttributeID attribute_id, uint32_t offset, std::optional size) { + if (offset == 0) { + if (size.has_value() && attribute_id.is_indirect()) { + auto calculated_size = attribute_id.size(); + if (calculated_size.has_value() && calculated_size.value() == size.value()) { + return attribute_id; + } + } + } else if (offset > AG::IndirectNode::MaximumOffset) { + AG::precondition_failure("invalid offset: %u, %lu", offset, size.value()); + } + + auto current_subgraph = AG::Subgraph::current_subgraph(); + if (current_subgraph == nullptr) { + AG::precondition_failure("no subgraph active while adding attribute"); + } + + AG::data::ptr indirect_node = + current_subgraph->graph()->add_indirect_attribute(*current_subgraph, attribute_id, offset, size, false); + return AG::AttributeID(indirect_node); // TODO: check adds kind +} + +} // namespace + +AGAttribute AGGraphCreateOffsetAttribute(AGAttribute attribute, uint32_t offset) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + return create_offset_attribute(attribute_id, offset, std::optional()); +} + +AGAttribute AGGraphCreateOffsetAttribute2(AGAttribute attribute, uint32_t offset, size_t size) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + return create_offset_attribute(attribute_id, offset, std::optional(size)); +} + +#pragma mark - Updates + +void AGGraphInvalidate(AGGraphRef graph) { + if (graph->context.invalidated()) { + return; + } + graph->context.~Context(); // TODO: check this goes here + graph->context.set_invalidated(true); +} + +void AGGraphInvalidateAllValues(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().value_mark_all(); +} + +void AGGraphInvalidateValue(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + subgraph->graph()->value_mark(attribute_id.to_ptr()); +} + +void AGGraphSetInvalidationCallback(AGGraphRef graph, + void (*callback)(const void *context AG_SWIFT_CONTEXT, AGAttribute) + AG_SWIFT_CC(swift), + const void *callback_context) { + auto context = AG::Graph::Context::from_cf(graph); + context->set_invalidation_callback(AG::ClosureFunctionAV(callback, callback_context)); +} + +void AGGraphUpdateValue(AGAttribute attribute, uint8_t options) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + subgraph->graph()->update_attribute(attribute_id, options); +} + +void AGGraphCancelUpdate() { + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + update_stack_ptr.get()->cancel(); +} + +bool AGGraphCancelUpdateIfNeeded() { + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + if (update_stack_ptr.get()->cancelled()) { + return true; + } + + if (update_stack_ptr.get()->graph()->passed_deadline()) { + update_stack_ptr.get()->cancel(); + return true; + } + + return false; +} + +bool AGGraphUpdateWasCancelled() { + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() != 0 || update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + return update_stack_ptr.get()->cancelled(); +} + +void AGGraphSetUpdate(const void *update) { + AG::Graph::set_current_update(util::tagged_ptr((AG::Graph::UpdateStack *)update)); +} + +void AGGraphSetUpdateCallback(AGGraphRef graph, + void (*callback)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *callback_context) { + auto context = AG::Graph::Context::from_cf(graph); + context->set_update_callback(AG::ClosureFunctionVV(callback, callback_context)); +} + +const void *AGGraphClearUpdate() { + auto current_update = AG::Graph::current_update(); + if (current_update != nullptr && current_update.tag() == 0) { + AG::Graph::set_current_update(current_update.with_tag(true)); // TODO: tag is cleared... + } + return (const void *)current_update.value(); +} + +uint64_t AGGraphGetDeadline(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + return context->deadline(); +} + +void AGGraphSetDeadline(AGGraphRef graph, uint64_t deadline) { + auto context = AG::Graph::Context::from_cf(graph); + context->set_deadline(deadline); +} + +bool AGGraphHasDeadlinePassed() { + auto current_update = AG::Graph::current_update(); + if (current_update != nullptr) { + return current_update.get()->graph()->passed_deadline(); + } + return false; +} + +void AGGraphSetNeedsUpdate(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + context->set_needs_update(); +} + +void AGGraphWithUpdate(AGAttribute attribute, void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *context) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (attribute_id.is_nil()) { + // TODO: AGGraphWithoutUpdate + auto update = AG::Graph::current_update(); + AG::Graph::set_current_update(update != nullptr ? update.with_tag(true) : nullptr); + function(context); + AG::Graph::set_current_update(update); + return; + } + + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + subgraph->graph()->with_update(attribute_id.to_ptr(), AG::ClosureFunctionVV(function, context)); +} + +void AGGraphWithoutUpdate(void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *context) { + AG::Graph::without_update(AG::ClosureFunctionVV(function, context)); +} + +void AGGraphWithMainThreadHandler(AGGraphRef graph, + void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *body_context, + void (*main_thread_handler)(const void *context AG_SWIFT_CONTEXT, + void (*trampoline_thunk)(const void *), + const void *trampoline) AG_SWIFT_CC(swift), + const void *main_thread_handler_context) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().with_main_handler(AG::ClosureFunctionVV(function, body_context), main_thread_handler, + main_thread_handler_context); +} + +#pragma mark - Values + +namespace { + +inline AGValue get_value(AG::AttributeID attribute_id, uint32_t subgraph_id, AGValueOptions options, + const AG::swift::metadata &metadata) { + if (!(options & AGValueOptions0x04)) { + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() == 0 && update_stack_ptr.get() != nullptr) { + auto update_stack = update_stack_ptr.get(); + + auto graph = update_stack->graph(); + auto frame = update_stack->frames().back(); + + uint8_t state = 0; + void *value = + graph->input_value_ref(frame.attribute, attribute_id, subgraph_id, options & 3, metadata, &state); + + // TODO: check if this is state or changed + return {value, (state & 1) == 1}; + } + } + + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute_id); + } + + uint8_t state = 0; + void *value = subgraph->graph()->value_ref(attribute_id, subgraph_id, metadata, &state); + + return {value, state & 1 ? true : false}; +} + +} // namespace + +bool AGGraphHasValue(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + return subgraph->graph()->value_exists(attribute_id.to_ptr()); +} + +AGValue AGGraphGetValue(AGAttribute attribute, AGValueOptions options, AGTypeID type) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + auto metadata = reinterpret_cast(type); + return get_value(attribute_id, 0, options, *metadata); +} + +AGValueState AGGraphGetValueState(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + return subgraph->graph()->value_state(attribute_id); +} + +AGValue AGGraphGetWeakValue(AGWeakAttribute attribute, AGValueOptions options, AGTypeID type) { + auto weak_attribute_id = AG::WeakAttributeID::from_cf(attribute); + auto attribute_id = weak_attribute_id.evaluate(); + if (attribute_id.is_nil()) { + return {nullptr, false}; + } + + auto metadata = reinterpret_cast(type); + return get_value(attribute_id, weak_attribute_id.subgraph_id(), options, *metadata); +} + +bool AGGraphSetValue(AGAttribute attribute, const void *value, AGTypeID type) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + auto metadata = reinterpret_cast(type); + return subgraph->graph()->value_set(attribute_id.to_ptr(), *metadata, value); +} + +AGGraphUpdateStatus AGGraphPrefetchValue(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + if (subgraph->graph()->passed_deadline()) { + return AGGraphUpdateStatusChanged; + } + + auto resolved = attribute_id.resolve(AG::TraversalOptions::AssertNotNil); + + // TODO: typed options + return subgraph->graph()->update_attribute(resolved.attribute(), 6); +} + +#pragma mark - Inputs + +AGValue AGGraphGetInputValue(AGAttribute attribute, AGAttribute input_attribute, AGValueOptions options, + AGTypeID type) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (options & AGValueOptions0x04 || attribute_id.is_nil()) { + return AGGraphGetValue(input_attribute, options, type); + } + + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + auto input_attribute_id = AG::AttributeID::from_storage(input_attribute); + auto metadata = reinterpret_cast(type); + + uint8_t state = 0; + void *value = subgraph->graph()->input_value_ref(attribute, input_attribute_id, 0, options & 3, *metadata, &state); + + // TODO: check if this is state or changed + return {value, (state & 1) == 1}; +} + +uint32_t AGGraphAddInput(AGAttribute attribute, AGAttribute input, AGInputOptions options) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + if (!attribute_id.is_direct()) { + AG::precondition_failure("non-direct attribute id: %u", attribute); + } + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + auto input_attribute_id = AG::AttributeID::from_storage(input); + input_attribute_id.validate_data_offset(); + + if (input_attribute_id.subgraph() != nullptr && input_attribute_id.subgraph()->graph() != subgraph->graph()) { + AG::precondition_failure("accessing attribute in a different namespace: %u", input); + } + + return subgraph->graph()->add_input(attribute_id.to_ptr(), input_attribute_id, false, options); +} + +bool AGGraphAnyInputsChanged(const AGAttribute *exclude_attributes, uint64_t exclude_attributes_count) { + // TODO: tag must be whether frames is empty or not + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() != 0 || update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + auto update_stack = update_stack_ptr.get(); + auto frame = update_stack->frames().back(); + + return update_stack->graph()->any_inputs_changed( + frame.attribute, reinterpret_cast(exclude_attributes), exclude_attributes_count); +} + +#pragma mark - Outputs + +void *AGGraphGetOutputValue(AGTypeID type) { + // TODO: tag must be whether frames is empty or not + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() != 0 || update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + auto update_stack = update_stack_ptr.get(); + auto frame = update_stack->frames().back(); + + auto graph = update_stack->graph(); + auto metadata = reinterpret_cast(type); + return graph->output_value_ref(frame.attribute, *metadata); +} + +void AGGraphSetOutputValue(const void *value, AGTypeID type) { + // TODO: tag must be whether frames is empty or not + auto update_stack_ptr = AG::Graph::current_update(); + if (update_stack_ptr.tag() != 0 || update_stack_ptr.get() == nullptr) { + AG::precondition_failure("no attribute updating"); + } + + auto update_stack = update_stack_ptr.get(); + auto frame = update_stack->frames().back(); + + if (!frame.attribute->state().is_updating()) { + // TODO: change to is_evaluating() + AG::precondition_failure("writing attribute that is not evaluating: %", frame.attribute); + } + + auto graph = update_stack->graph(); + auto metadata = reinterpret_cast(type); + graph->value_set_internal(frame.attribute, *frame.attribute.get(), value, *metadata); +} + +#pragma mark - Tracing + +bool AGGraphIsTracingActive(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + return context->graph().is_tracing_active(); +} + +void AGGraphPrepareTrace(AGGraphRef graph, void *trace_vtable, void *trace) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().remove_trace(0); + + auto external_trace = new ExternalTrace(reinterpret_cast(trace_vtable), trace); + context->graph().prepare_trace(*external_trace); +} + +uint64_t AGGraphAddTrace(AGGraphRef graph, void *interface, void *trace_info) { + auto context = AG::Graph::Context::from_cf(graph); + auto trace = new ExternalTrace(reinterpret_cast(interface), trace_info); + context->graph().add_trace(trace); + return trace->trace_id(); +} + +void AGGraphRemoveTrace(AGGraphRef graph, uint64_t trace_id) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().remove_trace(trace_id); +} + +void AGGraphStartTracing(AGGraphRef graph, uint32_t tracing_flags) { AGGraphStartTracing2(graph, tracing_flags, 0); } + +void AGGraphStartTracing2(AGGraphRef graph, uint32_t tracing_flags, uint32_t unknown) { + if (!graph) { + AG::Graph::all_start_tracing(tracing_flags, {}); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().start_tracing(tracing_flags, {}); +} + +void AGGraphStopTracing(AGGraphRef graph) { + if (!graph) { + AG::Graph::all_stop_tracing(); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().stop_tracing(); +} + +void AGGraphSyncTracing(AGGraphRef graph) { + if (!graph) { + AG::Graph::all_sync_tracing(); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().sync_tracing(); +} + +CFStringRef AGGraphCopyTracePath(AGGraphRef graph) { + if (graph == nullptr) { + return AG::Graph::all_copy_trace_path(); + } + auto context = AG::Graph::Context::from_cf(graph); + return context->graph().copy_trace_path(); +} + +void AGGraphSetTrace(AGGraphRef graph, void *trace_vtable, void *trace) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().remove_trace(0); + + auto external_trace = new ExternalTrace(0, reinterpret_cast(trace_vtable), trace); + context->graph().add_trace(external_trace); +} + +void AGGraphResetTrace(AGGraphRef graph) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().remove_trace(0); +} + +bool AGGraphTraceEventEnabled(AGGraphRef graph, uint32_t event_id) { + auto context = AG::Graph::Context::from_cf(graph); + for (auto trace : context->graph().traces()) { + if (trace->named_event_enabled(event_id)) { + return true; + } + } + return false; +} + +void AGGraphAddTraceEvent(AGGraphRef graph, const char *event_name, const void *value, AGTypeID type) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().foreach_trace([&context, &event_name, &value, &type](AG::Trace &trace) { + trace.custom_event(*context, event_name, value, *reinterpret_cast(type)); + }); +} + +void AGGraphAddNamedTraceEvent(AGGraphRef graph, uint32_t event_id, uint32_t num_event_args, const uint64_t *event_args, + CFDataRef data, uint32_t arg6) { + auto context = AG::Graph::Context::from_cf(graph); + context->graph().foreach_trace([&context, &event_id, &num_event_args, &event_args, &data, &arg6](AG::Trace &trace) { + trace.named_event(*context, event_id, num_event_args, event_args, data, arg6); + }); +} + +namespace NamedEvents { + +static os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; +static AG::vector, 0, uint32_t> *names; + +} // namespace NamedEvents + +const char *AGGraphGetTraceEventName(uint32_t event_id) { + const char *event_name = nullptr; + + os_unfair_lock_lock(&NamedEvents::lock); + if (NamedEvents::names != nullptr && event_id < NamedEvents::names->size()) { + event_name = (*NamedEvents::names)[event_id].second; + } + os_unfair_lock_unlock(&NamedEvents::lock); + + return event_name; +} + +const char *AGGraphGetTraceEventSubsystem(uint32_t event_id) { + const char *event_subsystem = nullptr; + + os_unfair_lock_lock(&NamedEvents::lock); + if (NamedEvents::names != nullptr && event_id < NamedEvents::names->size()) { + event_subsystem = (*NamedEvents::names)[event_id].first; + } + os_unfair_lock_unlock(&NamedEvents::lock); + + return event_subsystem; +} + +uint32_t AGGraphRegisterNamedTraceEvent(const char *event_name, const char *event_subsystem) { + os_unfair_lock_lock(&NamedEvents::lock); + + if (!NamedEvents::names) { + NamedEvents::names = new AG::vector, 0, uint32_t>(); + NamedEvents::names->push_back({0, 0}); // Disallow 0 as event ID + } + + uint32_t event_id = NamedEvents::names->size(); + if (event_subsystem != nullptr) { + event_subsystem = strdup(event_subsystem); + } + event_name = strdup(event_name); + NamedEvents::names->push_back({event_subsystem, event_name}); + + os_unfair_lock_unlock(&NamedEvents::lock); + + return event_id; +} + +#pragma mark - Profiler + +bool AGGraphIsProfilingEnabled(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + return subgraph->graph()->is_profiling_enabled(); +} + +uint64_t AGGraphBeginProfileEvent(AGAttribute attribute, const char *event_name) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + auto resolved = attribute_id.resolve(AG::TraversalOptions::AssertNotNil); + return subgraph->graph()->begin_profile_event(resolved.attribute().to_ptr(), event_name); +} + +void AGGraphEndProfileEvent(AGAttribute attribute, const char *event_name, uint64_t start_time, bool changed) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (!subgraph) { + AG::precondition_failure("no graph: %u", attribute); + } + + auto resolved = attribute_id.resolve(AG::TraversalOptions::AssertNotNil); + subgraph->graph()->end_profile_event(resolved.attribute().to_ptr(), event_name, start_time, changed); +} + +void AGGraphStartProfiling(AGGraphRef graph) { + if (!graph) { + AG::Graph::all_start_profiling(1); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().start_profiling(1); +} + +void AGGraphStopProfiling(AGGraphRef graph) { + if (!graph) { + AG::Graph::all_stop_profiling(); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().stop_profiling(); +} + +void AGGraphMarkProfile(AGGraphRef graph, const char *name) { + if (!graph) { + AG::Graph::all_mark_profile(name); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + uint32_t event_id = context->graph().intern_key(name); + context->graph().mark_profile(event_id, 0); +} + +void AGGraphResetProfile(AGGraphRef graph) { + if (!graph) { + AG::Graph::all_reset_profile(); + return; + } + auto context = AG::Graph::Context::from_cf(graph); + context->graph().reset_profile(); +} + +#pragma mark - Description + +CFTypeRef AGGraphDescription(AGGraphRef graph, CFDictionaryRef options) { + if (graph == nullptr) { + return AG::Graph::description(nullptr, options); + } + auto context = AG::Graph::Context::from_cf(graph); + return AG::Graph::description(&context->graph(), options); +} + +void AGGraphArchiveJSON(const char *filename) { AG::Graph::write_to_file(nullptr, filename, false); } + +void AGGraphArchiveJSON2(const char *filename, bool exclude_values) { + AG::Graph::write_to_file(nullptr, filename, exclude_values); +} diff --git a/Sources/ComputeCxx/Graph/AGGraph.h b/Sources/ComputeCxx/Graph/AGGraph.h index 3b1d278..84a6b43 100644 --- a/Sources/ComputeCxx/Graph/AGGraph.h +++ b/Sources/ComputeCxx/Graph/AGGraph.h @@ -1,14 +1,501 @@ #pragma once #include +#include +#include #include "AGSwiftSupport.h" +#include "Attribute/AGAttribute.h" +#include "Attribute/AGWeakAttribute.h" +#include "Swift/AGType.h" CF_ASSUME_NONNULL_BEGIN CF_EXTERN_C_BEGIN +// MARK: CFType + typedef struct CF_BRIDGED_TYPE(id) AGGraphStorage *AGGraphRef AG_SWIFT_NAME(Graph); +typedef void *AGUnownedGraphRef AG_SWIFT_STRUCT; +typedef struct AGGraphContextStorage *AGUnownedGraphContextRef AG_SWIFT_STRUCT; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +CFTypeID AGGraphGetTypeID(); + +// MARK: Graph Context + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphRef AGGraphCreate() CF_SWIFT_NAME(AGGraphRef.init()); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphRef AGGraphCreateShared(AGGraphRef _Nullable graph) CF_SWIFT_NAME(AGGraphRef.init(shared:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGUnownedGraphRef AGGraphGetGraphContext(AGGraphRef graph) CF_SWIFT_NAME(getter:AGGraphRef.graphContext(self:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphRef AGGraphContextGetGraph(AGUnownedGraphContextRef context) + CF_SWIFT_NAME(getter:AGUnownedGraphContextRef.graph(self:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *AGGraphGetContext(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetContext(); + +// MARK: Counter + +typedef CF_ENUM(uint32_t, AGGraphCounterQuery) { + AGGraphCounterQueryNodeCount, + AGGraphCounterQueryTransactionCount, + AGGraphCounterQueryUpdateCount, + AGGraphCounterQueryChangeCount, + AGGraphCounterQueryContextID, + AGGraphCounterQueryGraphID, + AGGraphCounterQueryContextThreadUpdating, + AGGraphCounterQueryThreadUpdating, + AGGraphCounterQueryContextNeedsUpdate, + AGGraphCounterQueryNeedsUpdate, + AGGraphCounterQueryMainThreadUpdateCount, + AGGraphCounterQueryNodeTotalCount, + AGGraphCounterQuerySubgraphCount, + AGGraphCounterQuerySubgraphTotalCount, +}; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint64_t AGGraphGetCounter(AGGraphRef graph, AGGraphCounterQuery query) CF_SWIFT_NAME(AGGraphRef.counter(self:for:)); + +// MARK: Subgraphs + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphBeginDeferringSubgraphInvalidation(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphEndDeferringSubgraphInvalidation(AGGraphRef graph, bool was_deferring_subgraph_invalidation); + +// MARK: Attribute types + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint32_t AGGraphInternAttributeType(AGUnownedGraphRef graph, AGTypeID type, + const void *_Nonnull (*_Nonnull intern)(const void *context AG_SWIFT_CONTEXT) + AG_SWIFT_CC(swift), + const void *context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphVerifyType(AGAttribute attribute, AGTypeID type); + +// MARK: Attributes + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphCreateAttribute(uint32_t type_id, const void *body, const void *_Nullable value); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphRef AGGraphGetAttributeGraph(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttributeInfo AGGraphGetAttributeInfo(AGAttribute attribute) CF_SWIFT_NAME(getter:AGAttribute.info(self:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttributeFlags AGGraphGetFlags(AGAttribute attribute) CF_SWIFT_NAME(getter:AGAttribute.flags(self:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetFlags(AGAttribute attribute, AGAttributeFlags flags) CF_SWIFT_NAME(setter:AGAttribute.flags(self:_:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphMutateAttribute(AGAttribute attribute, AGTypeID type, bool invalidating, + void (*modify)(const void *context AG_SWIFT_CONTEXT, void *body) AG_SWIFT_CC(swift), + const void *context); + +typedef CF_OPTIONS(uint32_t, AGSearchOptions) { + AGSearchOptionsSearchInputs = 1 << 0, + AGSearchOptionsSearchOutputs = 1 << 1, + AGSearchOptionsTraverseGraphContexts = 1 << 2, +}; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphSearch(AGAttribute attribute, AGSearchOptions options, + bool (*predicate)(const void *context AG_SWIFT_CONTEXT, AGAttribute attribute) AG_SWIFT_CC(swift), + const void *context); + +// MARK: Cached attributes + +typedef CF_OPTIONS(uint8_t, AGCachedValueOptions) { AGCachedValueOptionsNone = 0 }; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *AGGraphReadCachedAttribute(uint64_t identifier, AGTypeID type, void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute attribute, bool *_Nullable changed_out, + uint32_t (*closure)(const void *context AG_SWIFT_CONTEXT, + AGUnownedGraphRef graph_context) AG_SWIFT_CC(swift), + const void *closure_context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *_Nullable AGGraphReadCachedAttributeIfExists(uint64_t identifier, AGTypeID type, void *body, AGTypeID value_type, + AGCachedValueOptions options, AGAttribute attribute, + bool *_Nullable changed_out); + +// MARK: Current attribute + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphGetCurrentAttribute(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphCurrentAttributeWasModified(); + +// MARK: Indirect attributes + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphCreateIndirectAttribute(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphCreateIndirectAttribute2(AGAttribute attribute, size_t size); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphGetIndirectAttribute(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetIndirectAttribute(AGAttribute attribute, AGAttribute source); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphResetIndirectAttribute(AGAttribute attribute, bool non_nil); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphGetIndirectDependency(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetIndirectDependency(AGAttribute attribute, AGAttribute dependency); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphRegisterDependency(AGAttribute dependency, uint8_t input_edge_flags); + +// MARK: Offset attributes + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphCreateOffsetAttribute(AGAttribute attribute, uint32_t offset); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGGraphCreateOffsetAttribute2(AGAttribute attribute, uint32_t offset, size_t size); + +// MARK: Updates + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphInvalidate(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphInvalidateAllValues(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphInvalidateValue(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetInvalidationCallback(AGGraphRef graph, + void (*callback)(const void *context AG_SWIFT_CONTEXT, AGAttribute) + AG_SWIFT_CC(swift), + const void *callback_context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphUpdateValue(AGAttribute attribute, uint8_t options); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphCancelUpdate(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphCancelUpdateIfNeeded(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphUpdateWasCancelled(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetUpdate(const void *update); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetUpdateCallback(AGGraphRef graph, + void (*callback)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *callback_context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +const void *AGGraphClearUpdate(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint64_t AGGraphGetDeadline(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetDeadline(AGGraphRef graph, uint64_t deadline); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphHasDeadlinePassed(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetNeedsUpdate(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphWithUpdate(AGAttribute attribute, void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphWithoutUpdate(void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphWithMainThreadHandler(AGGraphRef graph, + void (*function)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *body_context, + void (*main_thread_handler)(const void *context AG_SWIFT_CONTEXT, + void (*trampoline_thunk)(const void *), + const void *trampoline) AG_SWIFT_CC(swift), + const void *main_thread_handler_context); + +// MARK: Values + +typedef struct AGValue { + const void *value; + bool changed; +} AGValue; + +typedef CF_OPTIONS(uint8_t, AGChangedValueFlags) { + AGChangedValueFlagsChanged = 1, +}; + +typedef uint8_t AGValueState; + +typedef CF_OPTIONS(uint32_t, AGValueOptions) { + // options & 3 == AG::InputEdge::Flags + AGValueOptionsInputEdgeFlagsUnprefetched = 1 << 0, + AGValueOptionsInputEdgeFlagsUnknown1 = 1 << 1, + + AGValueOptions0x04 = 1 << 2, + + // AGValueOptionsUnknown1 = 1 << 0, + // AGValueOptionsValidateDirect = 1 << 1, + // AGValueOptionsUnknown3 = 1 << 2, + // AGValueOptionsResolveIndirect = 1 << 3, + // AGValueOptionsResolveWeak = 1 << 4, +}; + +typedef CF_OPTIONS(uint32_t, AGGraphUpdateStatus) { + AGGraphUpdateStatusNoChange = 0, + AGGraphUpdateStatusChanged = 1, + AGGraphUpdateStatusOption2 = 2, + AGGraphUpdateStatusNeedsCallMainHandler = 3, +}; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphHasValue(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGValue AGGraphGetValue(AGAttribute attribute, AGValueOptions options, AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGValueState AGGraphGetValueState(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGValue AGGraphGetWeakValue(AGWeakAttribute attribute, AGValueOptions options, AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphSetValue(AGAttribute attribute, const void *value, AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphUpdateStatus AGGraphPrefetchValue(AGAttribute attribute); + +// MARK: Inputs + +typedef CF_OPTIONS(uint32_t, AGInputOptions) { + AGInputOptionsNone = 0, +}; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGValue AGGraphGetInputValue(AGAttribute attribute, AGAttribute input_attribute, AGValueOptions options, AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint32_t AGGraphAddInput(AGAttribute attribute, AGAttribute input, AGInputOptions options); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphAnyInputsChanged(const AGAttribute *exclude_attributes, uint64_t exclude_attributes_count); + +// MARK: Outputs + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void *_Nullable AGGraphGetOutputValue(AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetOutputValue(const void *value, AGTypeID type); + +// MARK: Tracing + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphIsTracingActive(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphPrepareTrace(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint64_t AGGraphAddTrace(AGGraphRef graph, void *interface, void *trace_info); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphRemoveTrace(AGGraphRef graph, uint64_t trace_id); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphStartTracing(AGGraphRef graph, uint32_t tracing_flags); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphStartTracing2(AGGraphRef graph, uint32_t tracing_flags, uint32_t unknown); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphStopTracing(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSyncTracing(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +CFStringRef AGGraphCopyTracePath(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphSetTrace(AGGraphRef graph, void *trace_vtable, void *trace); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphResetTrace(AGGraphRef graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphTraceEventEnabled(AGGraphRef graph, uint32_t event_id); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphAddTraceEvent(AGGraphRef graph, const char *event_name, const void *value, AGTypeID type); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphAddNamedTraceEvent(AGGraphRef graph, uint32_t event_id, uint32_t num_event_args, const uint64_t *event_args, + CFDataRef data, uint32_t arg6); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +const char *AGGraphGetTraceEventName(uint32_t event_id); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +const char *AGGraphGetTraceEventSubsystem(uint32_t event_id); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint32_t AGGraphRegisterNamedTraceEvent(const char *key, const char *name); + +// MARK: Profiler + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGGraphIsProfilingEnabled(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint64_t AGGraphBeginProfileEvent(AGAttribute attribute, const char *event_name); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphEndProfileEvent(AGAttribute attribute, const char *event_name, uint64_t start_time, bool changed); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphStartProfiling(AGGraphRef _Nullable graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphStopProfiling(AGGraphRef _Nullable graph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphMarkProfile(AGGraphRef _Nullable graph, const char *event_name); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphResetProfile(AGGraphRef _Nullable graph); + +// MARK: Description + +CF_EXPORT +CF_REFINED_FOR_SWIFT +CFTypeRef AGGraphDescription(AGGraphRef _Nullable graph, CFDictionaryRef options) + CF_SWIFT_NAME(AGGraphRef.description(_:options:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphArchiveJSON(const char *_Nullable filename); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGGraphArchiveJSON2(const char *filename, bool exclude_values); CF_EXTERN_C_END diff --git a/Sources/ComputeCxx/Graph/Context.cpp b/Sources/ComputeCxx/Graph/Context.cpp new file mode 100644 index 0000000..47f136c --- /dev/null +++ b/Sources/ComputeCxx/Graph/Context.cpp @@ -0,0 +1,137 @@ +#include "Context.h" + +#include "AGGraph-Private.h" +#include "Attribute/AttributeID.h" +#include "Subgraph/Subgraph.h" +#include "Trace/Trace.h" +#include "UniqueID/AGUniqueID.h" +#include "UpdateStack.h" + +namespace AG { + +Graph::Context::Context(Graph *graph) { + _graph = graph; + _context_info = nullptr; + _unique_id = AGMakeUniqueID(); + _deadline = UINT64_MAX; + + Graph::will_add_to_context(graph); + graph->_contexts_by_id.insert(_unique_id, this); + + _graph->foreach_trace([this](Trace &trace) { trace.created(*this); }); +} + +Graph::Context::~Context() { + _graph->foreach_trace([this](Trace &trace) { trace.destroy(*this); }); + + bool removed = _graph->_contexts_by_id.remove(_unique_id); + if (removed && _deadline != UINT64_MAX) { + uint64_t min_deadline = UINT64_MAX; + _graph->_contexts_by_id.for_each( + [](const uint64_t context_id, Context *const context, void *min_deadline_ref) { + if (context->_deadline < *(uint64_t *)min_deadline_ref) { + *(uint64_t *)min_deadline_ref = context->_deadline; + } + }, + &min_deadline); + _graph->set_deadline(min_deadline); + } + + if (_graph->_num_contexts != 1) { + auto batch = without_invalidating(_graph); + for (auto subgraph : _graph->subgraphs()) { + if (subgraph->context_id() == _unique_id) { + subgraph->invalidate_and_delete_(true); + } + } + // batch.~without_invalidating called here + } + + Graph::did_remove_from_context(_graph); +// _graph->_num_contexts -= 1; +// if (_graph && _graph->_num_contexts == 0) { +// _graph->~Graph(); +// } + +} + + +Graph::Context *Graph::Context::from_cf(AGGraphStorage *storage) { + if (storage->context._invalidated) { + precondition_failure("invalidated graph"); + } + return &storage->context; +} + +void Graph::Context::set_deadline(uint64_t deadline) { + if (_deadline == deadline) { + return; + } + _deadline = deadline; + _graph->foreach_trace([this, &deadline](Trace &trace) { trace.set_deadline(deadline); }); + + uint64_t min_deadline = UINT64_MAX; + _graph->_contexts_by_id.for_each( + [](const uint64_t context_id, Context *const context, void *min_deadline_ref) { + if (context->_deadline < *(uint64_t *)min_deadline_ref) { + *(uint64_t *)min_deadline_ref = context->_deadline; + } + }, + &min_deadline); + _graph->set_deadline(min_deadline); +} + +void Graph::Context::set_needs_update() { + if (_needs_update) { + return; + } + _graph->foreach_trace([this](Trace &trace) { trace.needs_update(*this); }); + _needs_update = true; + _graph->set_needs_update(true); +} + +bool Graph::Context::thread_is_updating() { + for (auto update_stack = current_update(); update_stack != nullptr; update_stack = update_stack.get()->previous()) { + if (update_stack.get()->graph() == _graph) { + return _graph->is_context_updating(_unique_id); + } + } + return false; +} + +void Graph::Context::call_invalidation(AttributeID attribute) { + _graph_version = _graph->_version; + + if (_invalidation_callback) { + auto old_update = current_update(); + if (old_update.value() != 0) { + set_current_update(old_update.with_tag(true)); + } + + _graph->foreach_trace([this, &attribute](Trace &trace) { trace.begin_invalidation(*this, attribute); }); + _invalidation_callback(attribute); + _graph->foreach_trace([this, &attribute](Trace &trace) { trace.end_invalidation(*this, attribute); }); + + set_current_update(old_update); + } +} + +void Graph::Context::call_update() { + if (!_needs_update) { + return; + } + _needs_update = false; + + if (_update_callback) { + + auto stack = UpdateStack(_graph, UpdateStack::Option::SetTag | UpdateStack::Option::InvalidateSubgraphs); + + _graph->foreach_trace([this](Trace &trace) { trace.begin_update(*this); }); + _update_callback(); + _graph->foreach_trace([this](Trace &trace) { trace.end_update(*this); }); + + // ~UpdateStack() called here + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/Context.h b/Sources/ComputeCxx/Graph/Context.h new file mode 100644 index 0000000..7b3d4dc --- /dev/null +++ b/Sources/ComputeCxx/Graph/Context.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "Attribute/AGAttribute.h" +#include "Attribute/AttributeID.h" +#include "Graph.h" +#include "Private/CFRuntime.h" + +CF_ASSUME_NONNULL_BEGIN + +struct AGGraphStorage; + +namespace AG { + +class Graph::Context { + private: + Graph *_graph; + const void *_context_info; + uint64_t _unique_id; + + ClosureFunctionAV _invalidation_callback; + ClosureFunctionVV _update_callback; + + uint64_t _deadline; + uint64_t _graph_version; + bool _needs_update; + bool _invalidated; // set to true after destru + + public: + Context(Graph *graph); + ~Context(); + + AGGraphStorage *to_cf() const { return reinterpret_cast((char *)this - sizeof(CFRuntimeBase)); }; + static Context *from_cf(AGGraphStorage *storage); + + Graph &graph() const { return *_graph; }; + + const void *context_info() const { return _context_info; }; + void set_context_info(const void *context_info) { _context_info = context_info; }; + + uint64_t unique_id() const { return _unique_id; }; + + void set_invalidation_callback(AG::ClosureFunctionAV callback) { + _invalidation_callback = callback; + } + void set_update_callback(AG::ClosureFunctionVV callback) { _update_callback = callback; } + + uint64_t deadline() const { return _deadline; }; + void set_deadline(uint64_t deadline); + + uint64_t graph_version() const { return _graph_version; }; + + bool needs_update() const { return _needs_update; }; + void set_needs_update(); + + bool invalidated() const { return _invalidated; }; + void set_invalidated(bool invalidated) { _invalidated = invalidated; }; + + bool thread_is_updating(); + + void call_invalidation(AttributeID attribute); + void call_update(); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Graph+Description.mm b/Sources/ComputeCxx/Graph/Graph+Description.mm new file mode 100644 index 0000000..2a34510 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Graph+Description.mm @@ -0,0 +1,1093 @@ +#include "Graph.h" + +#import + +#include + +#include "AGGraph.h" +#include "Attribute/AttributeIDList.h" +#include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" +#include "Attribute/Node/Node.h" +#include "Attribute/OffsetAttributeID.h" +#include "Graph/Profile/ProfileData.h" +#include "Subgraph/Subgraph.h" +#include "Swift/SwiftShims.h" +#include "Time/Time.h" +#include "Trace/Trace.h" +#include "Tree/TreeElement.h" +#include "UpdateStack.h" +#include "Utilities/FreeDeleter.h" +#include "Version/AGVersion.h" + +NSString *AGDescriptionFormat = @"format"; +NSString *AGDescriptionMaxFrames = @"max-frames"; +NSString *AGDescriptionIncludeValues = @"include-values"; +NSString *AGDescriptionTruncationLimit = @"truncation-limit"; + +namespace { + +NSString *escaped_string(NSString *string, NSUInteger truncation_limit) { + if ([string length] > truncation_limit) { + string = [[string substringToIndex:truncation_limit] stringByAppendingString:@"…"]; + } + return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]; +} + +} // namespace + +namespace AG { + +void Graph::print() { + NSString *description = (__bridge NSString *)description_graph_dot(nil); + if (description) { + const char *string = [description UTF8String]; + std::cout << string; + } +} + +void Graph::print_attribute(data::ptr node) { + NSString *desc = (__bridge NSString *)description(node); + if (desc) { + const char *string = [desc UTF8String]; + fprintf(stdout, "%s\n", string); + } +} + +void Graph::print_data() { + data::table::shared().print(); + data::zone::print_header(); + for (auto subgraph : _subgraphs) { + subgraph->data::zone::print(); + } +} + +void Graph::print_stack() { + auto update = current_update(); + if (update != 0) { + uint32_t update_stack_count = 0; + for (util::tagged_ptr update_stack = update; update_stack != nullptr; + update_stack = update_stack.get()->previous()) { + update_stack_count += 1; + } + uint32_t update_stack_index = update_stack_count - 1; + for (util::tagged_ptr update_stack = update; update_stack != nullptr; + update_stack = update_stack.get()->previous()) { + + for (auto frame_index = update_stack.get()->frames().size() - 1; frame_index >= 0; --frame_index) { + auto frame = update_stack.get()->frames()[frame_index]; + + uint32_t count = frame.attribute->state().update_count(); + uint32_t input_index = frame.num_pushed_inputs; + uint32_t num_inputs = frame.attribute->inputs().size(); + + const char *pending = frame.needs_update ? "P" : ""; + const char *cancelled = frame.cancelled ? "C" : ""; + const char *dirty = frame.attribute->state().is_dirty() ? "D" : ""; + const char *has_value = frame.attribute->state().is_value_initialized() ? "V" : ""; + + fprintf(stdout, "frame %d.%d: attribute %u; count=%d, index=%d/%d %s%s%s%s\n", update_stack_index, + (uint32_t)frame_index, frame.attribute.offset(), count, input_index, num_inputs, pending, + cancelled, dirty, has_value); + } + + update_stack_index -= 1; + } + } +} + +namespace { + +int cycle_verbosity() { + const char *print_cycles = getenv("AG_PRINT_CYCLES"); + if (print_cycles) { + return atoi(print_cycles); + } + return 1; +} + +int trap_cycles() { + const char *trap_cycles = getenv("AG_TRAP_CYCLES"); + if (trap_cycles) { + return atoi(trap_cycles) != 0; + } + return false; +} + +} // namespace + +void Graph::print_cycle(data::ptr node) { + foreach_trace([&node](Trace &trace) { trace.log_message("cycle detected through attribute: %u", node); }); + + static int verbosity = cycle_verbosity(); + if (verbosity >= 1) { + fprintf(stdout, "=== AttributeGraph: cycle detected through attribute %u ===\n", node.offset()); + if (verbosity >= 2) { + if (verbosity >= 3) { + vector, 0, uint64_t> nodes = {}; + collect_stack(nodes); + NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; + for (auto node : nodes) { + [indexSet addIndex:node.offset()]; + } + + NSMutableDictionary *dict = [NSMutableDictionary + dictionaryWithDictionary:@{AGDescriptionFormat : @"graph/dot", @"attribute-ids" : indexSet}]; + + NSString *desc = (NSString *)description(this, (__bridge CFMutableDictionaryRef)dict); + if (desc) { + const char *string = [desc UTF8String]; + std::cout << string; + } + std::cout << "=== Evaluation stack ===\n"; + } + + print_stack(); + std::cout << "===\n"; + + if (verbosity >= 4 /* && os_variant_has_internal_diagnostics() */) { + AGGraphArchiveJSON("cycle.ag-json"); + } + } + } + + static bool traps = trap_cycles(); + if (traps) { + precondition_failure("cyclic graph: %u", node); + } +} + +#pragma mark - Description + +CFStringRef Graph::description(data::ptr node) {} + +id Graph::description(Graph *graph, CFDictionaryRef options) { + NSString *format = ((__bridge NSDictionary *)options)[AGDescriptionFormat]; + if ([format isEqualToString:@"graph/dict"]) { + return (__bridge id)description_graph(graph, options); + } + if (graph && [format isEqualToString:@"graph/dot"]) { + return (__bridge id)graph->description_graph_dot(options); + } + if ([format hasPrefix:@"stack/"]) { + auto update = current_update(); + if (auto update_stack = update.get()) { + if ([format isEqualToString:@"stack/text"]) { + return (__bridge id)graph->description_stack(options); + } + if ([format isEqualToString:@"stack/nodes"]) { + return (__bridge id)graph->description_stack_nodes(options); + } + if ([format isEqualToString:@"stack/frame"]) { + return (__bridge id)graph->description_stack_frame(options); + } + } + } + return nil; +} + +CFDictionaryRef Graph::description_graph(Graph *graph_param, CFDictionaryRef options) { + NSNumber *include_values_number = ((__bridge NSDictionary *)options)[AGDescriptionIncludeValues]; + bool include_values = false; + if (include_values_number) { + include_values = [include_values_number boolValue]; + } + + NSNumber *truncation_limit_number = ((__bridge NSDictionary *)options)[AGDescriptionTruncationLimit]; + uint64_t truncation_limit = 1024; + if (truncation_limit_number) { + truncation_limit = [truncation_limit_number unsignedLongValue]; + } + + NSMutableArray *graph_dicts = [NSMutableArray array]; + + auto graph_indices_by_id = std::unordered_map(); + auto graph_stack = std::stack>(); + + auto push_graph = [&graph_dicts, &graph_indices_by_id, &graph_stack](Graph *graph) -> uint64_t { + auto found = graph_indices_by_id.find(graph->unique_id()); + if (found != graph_indices_by_id.end()) { + return found->second; + } + uint64_t index = [graph_dicts count]; + [graph_dicts addObject:[NSNull null]]; + graph_indices_by_id.emplace(graph->unique_id(), index); + graph_stack.push(graph); + return index; + }; + + if (graph_param) { + push_graph(graph_param); + } + NSArray *graph_ids = ((__bridge NSDictionary *)options)[@"graph_ids"]; + if (graph_ids && [graph_ids isKindOfClass:[NSArray class]]) { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + if ([graph_ids containsObject:[NSNumber numberWithUnsignedLong:graph->unique_id()]]) { + push_graph(graph); + } + } + all_unlock(); + } + NSNumber *all_graphs = ((__bridge NSDictionary *)options)[@"all_graphs"]; + if (all_graphs && [all_graphs boolValue]) { + all_lock(); + for (auto other_graph = _all_graphs; other_graph != nullptr; other_graph = other_graph->_next) { + if (other_graph != graph_param) { + push_graph(other_graph); + } + } + all_unlock(); + } + + while (!graph_stack.empty()) { + Graph *graph = graph_stack.top(); + graph_stack.pop(); + + NSMutableArray *node_dicts = [NSMutableArray array]; + NSMutableArray *edge_dicts = [NSMutableArray array]; + + auto type_indices_by_id = std::unordered_map(); + auto type_ids = vector(); + + auto node_indices_by_id = std::unordered_map, uint64_t>(); + + for (auto subgraph : graph->subgraphs()) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (!attribute.is_direct()) { + continue; + } + + uint64_t index = node_indices_by_id.size() + 1; + node_indices_by_id.emplace(attribute, index); + + auto node = attribute.to_node(); + + uint32_t type_id = node.type_id(); + uint64_t type_index; + auto found = type_indices_by_id.find(type_id); + if (found != type_indices_by_id.end()) { + type_index = found->second; + } else { + type_index = type_ids.size(); + type_ids.push_back(type_id); + type_indices_by_id.emplace(type_id, index); + } + + NSMutableDictionary *node_dict = [NSMutableDictionary dictionary]; + node_dict[@"type"] = [NSNumber numberWithUnsignedLong:type_index]; + node_dict[@"id"] = [NSNumber numberWithUnsignedLong:attribute]; + + const AttributeType &attribute_type = graph->attribute_type(node.type_id()); + if (node.state().is_self_initialized()) { + void *self = node.get_self(attribute_type); + if (auto desc = attribute_type.vt_self_description(self)) { + node_dict[@"desc"] = escaped_string((__bridge NSString *)desc, truncation_limit); + } + } + if (include_values && node.state().is_value_initialized()) { + void *value = node.get_value(); + if (auto value_desc = attribute_type.vt_value_description(value)) { + node_dict[@"value"] = escaped_string((__bridge NSString *)value_desc, truncation_limit); + } + } + + auto flags = attribute.to_node().value_state(); + if (flags) { + node_dict[@"flags"] = [NSNumber numberWithUnsignedInt:flags]; + } + + auto profile_data = graph->_profile_data.get(); + if (profile_data) { + auto found = profile_data->all_events().items_by_attribute().find(attribute.to_ptr()); + if (found != profile_data->all_events().items_by_attribute().end()) { + CFDictionaryRef item_json = profile_data->json_data(found->second, *graph); + if (item_json) { + node_dict[@"profile"] = (__bridge NSDictionary *)item_json; + } + } + if (profile_data->categories().size()) { + NSMutableDictionary *event_dicts = [NSMutableDictionary dictionary]; + for (auto &entry : profile_data->categories()) { + uint32_t event_id = entry.first; + auto found = entry.second.items_by_attribute().find(attribute.to_ptr()); + if (found != entry.second.items_by_attribute().end()) { + CFDictionaryRef item_json = profile_data->json_data(found->second, *graph); + if (item_json) { + NSString *event_name = + [NSString stringWithUTF8String:graph->key_name(event_id)]; + event_dicts[event_name] = (__bridge NSDictionary *)item_json; + } + } + } + if ([event_dicts count]) { + node_dict[@"events"] = event_dicts; + } + } + } + + [node_dicts addObject:node_dict]; + } + } + } + + for (auto subgraph : graph->subgraphs()) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (!attribute.is_direct()) { + continue; + } + + auto node = attribute.to_node(); + for (auto input_edge : node.inputs()) { + OffsetAttributeID resolved = input_edge.value.resolve(TraversalOptions::None); + if (resolved.attribute().is_direct()) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + auto src = node_indices_by_id.find(resolved.attribute().to_ptr())->second; + dict[@"src"] = [NSNumber numberWithUnsignedLong:src]; + auto dest = node_indices_by_id.find(attribute.to_ptr())->second; + dict[@"dest"] = [NSNumber numberWithUnsignedLong:dest]; + bool indirect = attribute.is_indirect(); + if (indirect) { + if (resolved.offset()) { + dict[@"offset"] = [NSNumber numberWithUnsignedLong:resolved.offset()]; + } + } + if (indirect || input_edge.is_always_enabled() || input_edge.is_changed() || + input_edge.is_unknown4() || input_edge.is_unprefetched()) { + dict[@"flags"] = @YES; // + } + + [edge_dicts addObject:dict]; + } + } + } + } + } + + // Add types for any removed nodes + if (graph->_profile_data) { + for (uint32_t type_id = 1; type_id < graph->_types.size(); ++type_id) { + if (type_indices_by_id.contains(type_id)) { + continue; + } + + auto removed_item = graph->_profile_data->all_events().removed_items_by_type_id().find(type_id); + if (removed_item != graph->_profile_data->all_events().removed_items_by_type_id().end()) { + if (!type_indices_by_id.contains(type_id)) { + auto index = type_ids.size(); + type_ids.push_back(type_id); + type_indices_by_id.try_emplace(type_id, index); + } + } else { + for (auto &entry : graph->_profile_data->categories()) { + auto &category = entry.second; + auto removed_item = category.removed_items_by_type_id().find(type_id); + if (removed_item != category.removed_items_by_type_id().end()) { + if (!type_indices_by_id.contains(type_id)) { + auto index = type_ids.size(); + type_ids.push_back(type_id); + type_indices_by_id.try_emplace(type_id, index); + } + } + } + } + } + } + + NSMutableArray *types = [NSMutableArray array]; + for (auto type_id : type_ids) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + const AttributeType &type = graph->attribute_type(type_id); + + dict[@"id"] = [NSNumber numberWithUnsignedInt:type_id]; + NSString *name = [NSString stringWithUTF8String:type.self_metadata().name(false)]; + dict[@"name"] = escaped_string(name, truncation_limit); + NSString *value = [NSString stringWithUTF8String:type.value_metadata().name(false)]; + dict[@"value"] = escaped_string(value, truncation_limit); + dict[@"size"] = + [NSNumber numberWithUnsignedLong:type.self_metadata().vw_size() + type.value_metadata().vw_size()]; + dict[@"flags"] = [NSNumber numberWithUnsignedInt:type.flags()]; + + auto profile_data = graph->_profile_data.get(); + if (profile_data) { + auto found = profile_data->all_events().removed_items_by_type_id().find(type_id); + if (found != profile_data->all_events().removed_items_by_type_id().end()) { + CFDictionaryRef item_json = profile_data->json_data(found->second, *graph); + if (item_json) { + dict[@"profile"] = (__bridge NSDictionary *)item_json; + } + } + if (profile_data->categories().size()) { + NSMutableDictionary *events = [NSMutableDictionary dictionary]; + for (auto &entry : profile_data->categories()) { + uint32_t event_id = entry.first; + auto found = entry.second.removed_items_by_type_id().find(type_id); + if (found != entry.second.removed_items_by_type_id().end()) { + CFDictionaryRef item_json = profile_data->json_data(found->second, *graph); + if (item_json) { + NSString *event_name = [NSString stringWithUTF8String:graph->key_name(event_id)]; + events[event_name] = (__bridge NSDictionary *)item_json; + } + } + } + if ([events count]) { + dict[@"events"] = events; + } + } + } + + [types addObject:dict]; + } + + // Subgraphs + + auto subgraph_indices_by_id = std::unordered_map(); + for (uint32_t index = 0, iter = graph->subgraphs().size(); iter > 0; --iter, ++index) { + subgraph_indices_by_id.try_emplace(graph->subgraphs()[index], index); + } + + NSMutableArray *subgraphs = [NSMutableArray array]; + for (auto subgraph : graph->subgraphs()) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[@"id"] = [NSNumber numberWithUnsignedInt:subgraph->subgraph_id()]; + dict[@"context_id"] = [NSNumber numberWithUnsignedLong:subgraph->context_id()]; + if (subgraph->validation_state() != Subgraph::ValidationState::Valid) { + dict[@"invalid"] = @YES; + } + + // Parents + NSMutableArray *parent_descriptions = [NSMutableArray array]; + for (auto parent : subgraph->parents()) { + id parent_description; + auto subgraph_index = subgraph_indices_by_id.find(parent); + if (subgraph_index != subgraph_indices_by_id.end()) { + parent_description = [NSNumber numberWithUnsignedLong:subgraph_index->second]; + } else { + uint64_t parent_graph_index; + auto parent_graph = graph_indices_by_id.find(parent->graph()->unique_id()); + if (parent_graph != graph_indices_by_id.end()) { + parent_graph_index = parent_graph->second; + } else { + parent_graph_index = push_graph(parent->graph()); + } + parent_description = @{ + @"graph" : [NSNumber numberWithUnsignedLong:parent_graph_index], + @"subgraph_id" : [NSNumber numberWithUnsignedInt:parent->subgraph_id()] + }; + } + [parent_descriptions addObject:parent_description]; + } + if ([parent_descriptions count]) { + dict[@"parents"] = parent_descriptions; + } + + // Children + NSMutableArray *child_descriptions = [NSMutableArray array]; + for (auto child : subgraph->children()) { + Subgraph *child_subgraph = child.subgraph(); + id child_description; + auto subgraph_index = subgraph_indices_by_id.find(child_subgraph); + if (subgraph_index != subgraph_indices_by_id.end()) { + child_description = [NSNumber numberWithUnsignedLong:subgraph_index->second]; + } else { + uint64_t child_graph_index; + auto child_graph = graph_indices_by_id.find(child_subgraph->graph()->unique_id()); + if (child_graph != graph_indices_by_id.end()) { + child_graph_index = child_graph->second; + } else { + child_graph_index = push_graph(child_subgraph->graph()); + } + child_description = @{ + @"graph" : [NSNumber numberWithUnsignedLong:child_graph_index], + @"subgraph_id" : [NSNumber numberWithUnsignedInt:child_subgraph->subgraph_id()] + }; + } + [child_descriptions addObject:child_description]; + } + if ([child_descriptions count]) { + dict[@"children"] = child_descriptions; + } + + // Nodes + NSMutableArray *nodes = [NSMutableArray array]; + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (!attribute.is_direct()) { + break; + } + + AGAttributeFlags subgraph_flags = attribute.to_node().subgraph_flags(); + auto found_node_index = node_indices_by_id.find(attribute.to_ptr()); + if (found_node_index != node_indices_by_id.end()) { + [nodes addObject:[NSNumber numberWithUnsignedLong:found_node_index->second]]; + if (subgraph_flags) { + NSMutableDictionary *node_dict = nodes[found_node_index->second]; + node_dict[@"subgraph_flags"] = [NSNumber numberWithUnsignedChar:subgraph_flags]; + nodes[found_node_index->second] = node_dict; + } + } + } + } + if ([nodes count]) { + dict[@"nodes"] = nodes; + } + + [subgraphs addObject:dict]; + } + + NSMutableArray *tree_dicts = nil; + if (auto map = graph->tree_data_elements()) { + tree_dicts = [NSMutableArray array]; + + auto tree_stack = std::stack, vector, 0, uint64_t>>(); + + auto tree_element_indices = std::unordered_map, uint64_t>(); + auto trees = AG::vector, 0ul, unsigned long>(); + + if (!graph->subgraphs().empty()) { + + for (auto subgraph : graph->subgraphs()) { + data::ptr tree_root = subgraph->tree_root(); + if (tree_root) { + tree_stack.push(tree_root); + } + while (!tree_stack.empty()) { + data::ptr tree = tree_stack.top(); + tree_stack.pop(); + + uint64_t tree_element_index; + auto found_tree_element = tree_element_indices.find(tree); + if (found_tree_element == tree_element_indices.end()) { + auto index = trees.size(); + tree_element_indices.try_emplace(tree, index); + trees.push_back(tree); + + if (tree->first_child) { + tree_stack.push(tree->first_child); + } + if (tree->next_sibling) { + tree_stack.push(tree->next_sibling); + } + } + } + + auto found_tree_data_element = map->find(subgraph); + if (found_tree_data_element != map->end()) { + found_tree_data_element->second.sort_nodes(); + } + } + + for (auto tree : trees) { + NSMutableDictionary *tree_dict = [NSMutableDictionary dictionary]; + + // TODO: what is creator key? + + if (tree->node.has_value() && tree->type != nullptr) { + OffsetAttributeID resolved = tree->node.resolve(TraversalOptions::ReportIndirectionInOffset); + if (resolved.attribute().is_direct()) { + tree_dict[@"node"] = [NSNumber + numberWithUnsignedLong:node_indices_by_id.find(resolved.attribute().to_ptr()) + ->second]; + if (resolved.offset() != 0) { + tree_dict[@"offset"] = [NSNumber numberWithUnsignedLong:resolved.offset() - 1]; // 3 + } + } + + tree_dict[@"desc"] = escaped_string([NSString stringWithUTF8String:tree->type->name(false)], + truncation_limit); // 4 + + if (tree->parent == nullptr) { + tree_dict[@"root"] = @YES; + } + } else if (tree->node.has_value() && tree->type == nullptr) { + tree_dict[@"node"] = [NSNumber + numberWithUnsignedLong:node_indices_by_id.find(tree->node.to_ptr())->second]; // 2 + } else { + if (tree->parent == nullptr) { + tree_dict[@"root"] = @YES; // 1 + } + } + + if (tree->flags) { + tree_dict[@"flags"] = [NSNumber numberWithUnsignedInt:tree->flags]; + } + + if (tree->first_child) { + NSMutableArray *children = [NSMutableArray array]; + for (data::ptr child = tree->first_child; child != nullptr; + child = child->next_sibling) { + [children + addObject:[NSNumber numberWithUnsignedLong:tree_element_indices.find(child)->second]]; + } + tree_dict[@"children"] = children; + } + + Subgraph *subgraph = TreeElementID(tree).subgraph(); + auto tree_data_element = map->find(subgraph); + if (tree_data_element != map->end()) { + + auto &data_nodes = tree_data_element->second.nodes(); + std::pair, data::ptr> *found = std::find_if( + data_nodes.begin(), data_nodes.end(), [&tree](auto node) { return node.first == tree; }); + + if (found != data_nodes.end()) { + NSMutableArray *nodes = [NSMutableArray array]; + for (auto node = found; node != data_nodes.end(); ++node) { + if (node->first != tree) { + break; + } + if (node->second) { + [nodes addObject:[NSNumber + numberWithUnsignedLong:node_indices_by_id.find(node->second) + ->second]]; + } + } + tree_dict[@"nodes"] = nodes; + } + } + + NSMutableDictionary *values = [NSMutableDictionary dictionary]; + for (data::ptr value = tree->first_value; value != nullptr; value = value->next) { + + OffsetAttributeID resolved_value = value->value.resolve(TraversalOptions::None); + if (resolved_value.attribute().is_direct()) { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + dict[@"node"] = + [NSNumber numberWithUnsignedLong:node_indices_by_id + .find(resolved_value.attribute().to_ptr()) + ->second]; + if (resolved_value.offset() != 0) { + dict[@"offset"] = [NSNumber numberWithUnsignedLong:resolved_value.offset()]; + } + + values[[NSString stringWithUTF8String:subgraph->graph()->key_name(value->key_id)]] = dict; + } + } + tree_dict[@"values"] = values; + + [tree_dicts addObject:tree_dict]; + } + } + } + + NSMutableDictionary *graph_dict = [NSMutableDictionary dictionary]; + graph_dict[@"id"] = [NSNumber numberWithUnsignedLong:graph->unique_id()]; + graph_dict[@"types"] = types; + graph_dict[@"nodes"] = node_dicts; + graph_dict[@"edges"] = edge_dicts; + graph_dict[@"subgraphs"] = subgraphs; + if (tree_dicts) { + graph_dict[@"trees"] = tree_dicts; + } + + graph_dict[@"transaction_count"] = [NSNumber numberWithUnsignedLong:graph->transaction_count()]; + if (auto profile_data = graph->_profile_data.get()) { + NSDictionary *json = (__bridge NSDictionary *)profile_data->json_data(profile_data->all_events(), *graph); + if (json) { + [graph_dict addEntriesFromDictionary:json]; + } + if (!profile_data->categories().empty()) { + NSMutableDictionary *events = [NSMutableDictionary dictionary]; + for (auto &category : profile_data->categories()) { + NSDictionary *json = (__bridge NSDictionary *)profile_data->json_data(category.second, *graph); + if (json) { + events[[NSString stringWithUTF8String:graph->key_name(category.first)]] = json; + } + } + if ([events count]) { + graph_dict[@"events"] = events; + } + } + } else { + graph_dict[@"update_count"] = [NSNumber numberWithUnsignedLong:graph->update_count()]; + graph_dict[@"change_count"] = [NSNumber numberWithUnsignedLong:graph->change_count()]; + } + + graph_dicts[graph_indices_by_id.find(graph->unique_id())->second] = graph_dict; + } + + NSMutableDictionary *description = [NSMutableDictionary dictionary]; + description[@"version"] = @2; // TODO: check + description[@"graphs"] = graph_dicts; + return (__bridge CFDictionaryRef)description; +} + +CFStringRef Graph::description_graph_dot(CFDictionaryRef _Nullable options) { + NSNumber *include_values_number = ((__bridge NSDictionary *)options)[AGDescriptionIncludeValues]; + bool include_values = false; + if (include_values_number) { + include_values = [include_values_number boolValue]; + } + + id attribute_ids = ((__bridge NSDictionary *)options)[@"attribute-ids"]; + if (![attribute_ids isKindOfClass:[NSIndexSet class]]) { + attribute_ids = nil; + } + + NSNumber *truncation_limit_number = ((__bridge NSDictionary *)options)[AGDescriptionTruncationLimit]; + uint64_t truncation_limit = 40; + if (truncation_limit_number) { + truncation_limit = [truncation_limit_number unsignedLongValue]; + } + + NSMutableString *result = [NSMutableString string]; + + [result appendString:@"digraph {\n"]; + + auto indirect_nodes = std::unordered_map, data::ptr>(); + + if (!subgraphs().empty()) { + for (auto subgraph : subgraphs()) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (!attribute.is_direct()) { + continue; + } + + if (!attribute_ids || [attribute_ids containsIndex:attribute]) { + + [result appendFormat:@" _%d[label=\"%d", AGAttribute(attribute), AGAttribute(attribute)]; + + Node &node = attribute.to_node(); + const AttributeType &node_type = attribute_type(node.type_id()); + if (node.state().is_self_initialized()) { + if (auto callback = node_type.vt_get_self_description_callback()) { + void *self = node.get_self(node_type); + if (auto desc = callback(&node_type, self)) { + [result appendString:@": "]; + [result appendString:escaped_string((__bridge NSString *)desc, truncation_limit)]; + } + } + } + if (include_values && node.state().is_value_initialized()) { + if (auto callback = node_type.vt_get_value_description_callback()) { + void *value = node.get_value(); + if (auto value_desc = callback(&node_type, value)) { + [result appendString:@" → "]; + [result + appendString:escaped_string((__bridge NSString *)value_desc, truncation_limit)]; + } + } + } + + double duration_fraction = 0.0; + if (auto profile_data = _profile_data.get()) { + auto &items_by_attribute = profile_data->all_events().items_by_attribute(); + auto found_item = items_by_attribute.find(attribute.to_ptr()); + if (found_item != items_by_attribute.end()) { + auto &item = found_item->second; + if (item.data().update_count) { + uint64_t update_count = item.data().update_count; + uint64_t update_total = item.data().update_total; + uint64_t all_update_total = profile_data->all_events().data().update_total; + double average_update_time = + absolute_time_to_seconds((double)update_total / (double)update_count); + duration_fraction = + ((double)update_total / (double)all_update_total) * 100.0; // duration fraction + [result appendFormat:@"\\n%.2g%%: %g × %.2fμs", duration_fraction, + (double)update_count, average_update_time * 1000000.0]; + } + } + } + [result appendString:@"\""]; + + bool filled = false; + + // heat colors + if (node.state().is_updating()) { + [result appendString:@" fillcolor=cyan"]; + filled = true; + } else if (duration_fraction > 10.0) { + [result appendString:@" fillcolor=orangered"]; + filled = true; + } else if (duration_fraction > 5.0) { + [result appendString:@" fillcolor=orange"]; + filled = true; + } else if (duration_fraction > 1.0) { + [result appendString:@" fillcolor=yellow"]; + filled = true; + } + + if (node.state().is_value_initialized() && !node.inputs().empty() && !node.outputs().empty()) { + if (filled) { + [result appendFormat:@" style=filled"]; + } + } else { + [result appendFormat:node.state().is_value_initialized() ? @" style=\"bold%s\"" + : @" style=\"dashed%s\"", + filled ? ",filled" : ""]; + } + + if (node.state().is_dirty()) { + [result appendString:@" color=red"]; + } + + [result appendString:@"];\n"]; + + for (auto input_edge : node.inputs()) { + + AttributeID resolved_input_attribute = + input_edge.value.resolve(TraversalOptions::None).attribute(); + if (resolved_input_attribute.is_direct() && + (!attribute_ids || [attribute_ids containsIndex:resolved_input_attribute])) { + + [result appendFormat:@" _%d -> _%d[", input_edge.value.to_ptr().offset(), + AGAttribute(attribute)]; + + // collect source inputs + AttributeID intermediate = input_edge.value; + while (intermediate.is_indirect()) { + indirect_nodes.try_emplace(intermediate.to_ptr(), + intermediate.to_ptr()); + + AttributeID source = intermediate.to_indirect_node().source().attribute(); + if (source.is_direct()) { + break; + } + intermediate = source.resolve(TraversalOptions::SkipMutableReference).attribute(); + } + + if (input_edge.is_changed()) { + [result appendString:@" color=red"]; + } + + Subgraph *input_subgraph = resolved_input_attribute.subgraph(); + Subgraph *attribute_subgraph = attribute.subgraph(); + uint64_t input_context_id = input_subgraph ? input_subgraph->context_id() : 0; + uint64_t attribute_context_id = + attribute_subgraph ? attribute_subgraph->context_id() : 0; + if (input_context_id != attribute_context_id) { + [result appendString:@" penwidth=2"]; + } + + if (input_edge.value.is_indirect()) { + uint32_t offset = + input_edge.value.resolve(TraversalOptions::SkipMutableReference).offset(); + [result appendFormat:@" label=\"@%d\"", offset]; + } + + [result appendString:@"];\n"]; + } + } + } + } + } + } + + for (auto entry : indirect_nodes) { + data::ptr indirect_node = entry.first; + + [result appendFormat:@" _%d[label=\"%d\" shape=box];\n", indirect_node.offset(), indirect_node.offset()]; + + OffsetAttributeID resolved_source = + indirect_node->source().attribute().resolve(TraversalOptions::SkipMutableReference); + [result appendFormat:@" _%d -> _%d[label=\"@%d\"];\n", resolved_source.attribute().to_ptr().offset(), + indirect_node.offset(), resolved_source.offset()]; + + if (indirect_node->is_mutable()) { + if (auto dependency = indirect_node->to_mutable().dependency()) { + [result + appendFormat:@" _%d -> _%d[color=blue];\n", AGAttribute(dependency), indirect_node.offset()]; + } + } + } + } + + [result appendString:@"}\n"]; + + return (__bridge CFStringRef)result; +} + +CFStringRef Graph::description_stack(CFDictionaryRef options) { + + NSMutableString *description = [NSMutableString string]; + + NSNumber *max_frames_number = [(__bridge NSDictionary *)options objectForKeyedSubscript:AGDescriptionMaxFrames]; + int max_frames = max_frames_number ? [max_frames_number unsignedIntValue] : -1; + + int frame_count = 0; + for (auto update = current_update(); update != nullptr; update = update.get()->previous()) { + auto &frames = update.get()->frames(); + for (auto frame = frames.rbegin(), end = frames.rend(); frame != end; ++frame) { + + const AttributeType &type = attribute_type(frame->attribute->type_id()); + [description appendFormat:@" #%d: %u %s -> %s\n", frame_count, frame->attribute.offset(), + type.self_metadata().name(false), type.value_metadata().name(false)]; + + if (frame_count == 0 && frame->attribute->inputs().size() > 0) { + [description appendString:@" -- inputs:\n"]; + for (auto input_edge : frame->attribute->inputs()) { + OffsetAttributeID resolved = input_edge.value.resolve(TraversalOptions::ReportIndirectionInOffset); + [description appendFormat:@" %u", AGAttribute(resolved.attribute())]; + if (resolved.offset() != 0) { + [description appendFormat:@"[@%d]", resolved.offset() - 1]; + } + if (resolved.attribute().is_direct()) { + const AttributeType &input_type = attribute_type(resolved.attribute().to_node().type_id()); + [description appendFormat:@" %s -> %s", input_type.self_metadata().name(false), + input_type.value_metadata().name(false)]; + } + if (input_edge.is_changed()) { + [description appendString:@", changed"]; + } + if (input_edge.is_always_enabled()) { + [description appendString:@", always-enabled"]; + } + if (input_edge.is_unprefetched()) { + [description appendString:@", unprefetched"]; + } + [description appendString:@"\n"]; + } + [description appendString:@" --\n"]; + } + + frame_count += 1; + if (frame_count >= max_frames) { + return (__bridge CFStringRef)description; + } + } + } + + return (__bridge CFStringRef)description; +} + +CFArrayRef Graph::description_stack_nodes(CFDictionaryRef options) { + NSMutableArray *nodes = [NSMutableArray array]; + + NSNumber *max_frames_number = [(__bridge NSDictionary *)options objectForKeyedSubscript:AGDescriptionMaxFrames]; + int max_frames = max_frames_number ? [max_frames_number unsignedIntValue] : -1; + + int frame_count = 0; + for (auto update = current_update(); update != nullptr; update = update.get()->previous()) { + auto &frames = update.get()->frames(); + for (auto frame = frames.rbegin(), end = frames.rend(); frame != end; ++frame) { + + [nodes addObject:[NSNumber numberWithUnsignedInt:frame->attribute.offset()]]; + + frame_count += 1; + if (frame_count >= max_frames) { + return (__bridge CFArrayRef)nodes; + } + } + } + + return (__bridge CFArrayRef)nodes; +} + +CFDictionaryRef Graph::description_stack_frame(CFDictionaryRef options) { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + + NSNumber *frame_index_number = [(__bridge NSDictionary *)options objectForKeyedSubscript:@"frame_index"]; + int frame_index = frame_index_number ? [frame_index_number unsignedIntValue] : -1; + + NSNumber *frame_node_number = [(__bridge NSDictionary *)options objectForKeyedSubscript:@"frame_node"]; + data::ptr frame_node = frame_node_number ? [frame_node_number unsignedIntValue] : 0; + + for (auto update = current_update(); update != nullptr; update = update.get()->previous()) { + int i = 0; + auto &frames = update.get()->frames(); + for (auto frame : frames) { + + if (i == frame_index || frame.attribute == frame_node) { + + dictionary[@"index"] = [NSNumber numberWithUnsignedLong:i]; + dictionary[@"node-id"] = [NSNumber numberWithUnsignedInt:frame.attribute.offset()]; + + const AttributeType &type = attribute_type(frame.attribute->type_id()); + AGSetTypeForKey((__bridge CFMutableDictionaryRef)dictionary, (__bridge CFStringRef) @"self-type", + &type.self_metadata()); + AGSetTypeForKey((__bridge CFMutableDictionaryRef)dictionary, (__bridge CFStringRef) @"value-type", + &type.value_metadata()); + + if (!frame.attribute->inputs().empty()) { + NSMutableArray *inputs = [NSMutableArray array]; + + for (auto input_edge : frame.attribute->inputs()) { + NSMutableDictionary *input_dictionary = [NSMutableDictionary dictionary]; + + input_dictionary[@"id"] = [NSNumber numberWithUnsignedInt:input_edge.value]; + + OffsetAttributeID resolved = + input_edge.value.resolve(TraversalOptions::ReportIndirectionInOffset); + + input_dictionary[@"node"] = [NSNumber numberWithUnsignedInt:resolved.attribute()]; + if (resolved.offset() != 0) { + input_dictionary[@"offset"] = [NSNumber numberWithUnsignedLong:resolved.offset() - 1]; + } + + if (resolved.attribute().is_direct()) { + const AttributeType &input_type = attribute_type(resolved.attribute().to_node().type_id()); + AGSetTypeForKey((__bridge CFMutableDictionaryRef)input_dictionary, + (__bridge CFStringRef) @"self-type", &input_type.self_metadata()); + AGSetTypeForKey((__bridge CFMutableDictionaryRef)input_dictionary, + (__bridge CFStringRef) @"value-type", &input_type.value_metadata()); + } + if (input_edge.is_changed()) { + input_dictionary[@"changed"] = @YES; + } + if (input_edge.is_always_enabled()) { + input_dictionary[@"always-enabled"] = @YES; + } + if (input_edge.is_unprefetched()) { + input_dictionary[@"prefetched"] = @NO; + } + } + + dictionary[@"inputs"] = inputs; + } + + return (__bridge CFDictionaryRef)dictionary; + } + + i += 1; + } + } + + return (__bridge CFDictionaryRef)dictionary; +} + +void Graph::write_to_file(Graph *graph, const char *_Nullable filename, bool exclude_values) { + NSDictionary *options = @{ + AGDescriptionFormat : @"graph/dict", + AGDescriptionIncludeValues : @(!exclude_values), + @"all_graphs" : @(graph == nullptr) + }; + NSDictionary *json = description(graph, (__bridge CFDictionaryRef)options); + if (!json) { + return; + } + + if (filename == nullptr) { + filename = "graph.ag-gzon"; + } + + NSData *data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; + + NSString *path = [NSString stringWithUTF8String:filename]; + if (*filename != '/') { + path = [NSTemporaryDirectory() stringByAppendingPathComponent:path]; + } + + NSError *error = nil; + if ([[path pathExtension] isEqualToString:@"ag-gzon"]) { + // Disassembly writes compressed data directly using gzwrite instead of creating an intermediate NSData object + data = [data compressedDataUsingAlgorithm:NSDataCompressionAlgorithmZlib error:&error]; + if (!data) { + fprintf(stdout, "Unable to write to \"%s\": %s\n", [path UTF8String], [[error description] UTF8String]); + return; + } + } + + if (![data writeToFile:path options:0 error:&error]) { + fprintf(stdout, "Unable to write to \"%s\": %s\n", [path UTF8String], [[error description] UTF8String]); + return; + } + + fprintf(stdout, "Wrote graph data to \"%s\".\n", [path UTF8String]); +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/Graph.cpp b/Sources/ComputeCxx/Graph/Graph.cpp index 1c16cdd..c9c948f 100644 --- a/Sources/ComputeCxx/Graph/Graph.cpp +++ b/Sources/ComputeCxx/Graph/Graph.cpp @@ -1,41 +1,2468 @@ #include "Graph.h" +#include +#include +#include +#include +#include +#include +#include + +#include "Attribute/AttributeIDList.h" #include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" #include "Attribute/Node/Node.h" +#include "Attribute/OffsetAttributeID.h" +#include "Attribute/WeakAttributeID.h" +#include "Comparison/AGComparison.h" +#include "Context.h" +#include "Debug/DebugServer.h" +#include "Errors/Errors.h" +#include "KeyTable.h" +#include "Log/Log.h" +#include "Profile/AGAppObserver.h" +#include "Profile/ProfileData.h" +#include "Profile/ProfileTrace.h" +#include "Subgraph/Subgraph.h" +#include "Swift/Metadata.h" +#include "Trace/Trace.h" +#include "TraceRecorder.h" +#include "Tree/TreeElement.h" +#include "UniqueID/AGUniqueID.h" +#include "UpdateStack.h" +#include "Utilities/FreeDeleter.h" +#include "Utilities/List.h" namespace AG { -void Graph::trace_assertion_failure(bool all_stop_tracing, const char *format, ...) { - // TODO: Not implemented +Graph *Graph::_all_graphs = nullptr; +os_unfair_lock Graph::_all_graphs_lock = OS_UNFAIR_LOCK_INIT; + +pthread_key_t Graph::_current_update_key = 0; + +Graph::Graph() + : _heap(nullptr, 0, 0), _type_ids_by_metadata(nullptr, nullptr, nullptr, nullptr, &_heap), + _contexts_by_id(nullptr, nullptr, nullptr, nullptr, &_heap), _num_contexts(1) { + + _unique_id = AGMakeUniqueID(); + + data::table::ensure_shared(); + + // what is this check doing? + if ((uintptr_t)this == 1) { + print(); + print_attribute(nullptr); + print_stack(); + print_data(); + write_to_file(this, nullptr, 0); + } + + static dispatch_once_t make_keys; + dispatch_once_f(&make_keys, nullptr, [](void *context) { + pthread_key_create(&Graph::_current_update_key, 0); + Subgraph::make_current_subgraph_key(); + }); + + _types.push_back(nullptr); // AGAttributeNullType + + static auto [profiler_flags, trace_flags, trace_subsystems] = + []() -> std::tuple, 0, uint64_t>> { + const char *debug_server = getenv("AG_DEBUG_SERVER"); + if (debug_server) { + uint32_t port = (uint32_t)strtol(debug_server, nullptr, 0); + DebugServer::start(port); + } + + uint32_t profiler_flags = false; + const char *profile_string = getenv("AG_PROFILE"); + if (profile_string) { + profiler_flags = atoi(profile_string) != 0; + } + + vector, 0, uint64_t> trace_subsystems = {}; + + uint32_t trace_flags = 0; + const char *trace_string = getenv("AG_TRACE"); + if (trace_string) { + char *endptr = nullptr; + trace_flags = (uint32_t)strtol(trace_string, &endptr, 0); + + if (endptr) { + const char *c = endptr + strspn(endptr, ", \t\n\f\r"); + while (c) { + size_t option_length = strcspn(c, ", \t\n\f\r"); + + char *option = (char *)malloc(option_length + 1); + memcpy(option, c, option_length); + option[option_length] = 0; + + if (strcasecmp(option, "enabled") == 0) { + trace_flags |= TracingFlags::Enabled; + free(option); + } else if (strcasecmp(option, "full") == 0) { + trace_flags |= TracingFlags::Full; + free(option); + } else if (strcasecmp(option, "backtrace") == 0) { + trace_flags |= TracingFlags::Backtrace; + free(option); + } else if (strcasecmp(option, "prepare") == 0) { + trace_flags |= TracingFlags::Prepare; + free(option); + } else if (strcasecmp(option, "custom") == 0) { + trace_flags |= TracingFlags::Custom; + free(option); + } else if (strcasecmp(option, "all") == 0) { + trace_flags |= TracingFlags::All; + free(option); + } else { + trace_subsystems.push_back(std::unique_ptr(option)); + } + + c += strspn(c + option_length, ", \t\n\f\r"); + } + } + } + + return {profiler_flags, trace_flags, std::move(trace_subsystems)}; + }(); + + if (trace_flags && !trace_subsystems.empty()) { + start_tracing(trace_flags, std::span((const char **)trace_subsystems.data(), trace_subsystems.size())); + } + if (profiler_flags) { + start_profiling(profiler_flags); + } + + all_lock(); + _next = _all_graphs; + _previous = nullptr; + _all_graphs = this; + if (_next) { + _next->_previous = this; + } + all_unlock(); +} + +Graph::~Graph() { + foreach_trace([this](Trace &trace) { + trace.end_trace(*this); + trace.~Trace(); + }); + + all_lock(); + if (_previous) { + _previous->_next = _next; + if (_next) { + _next->_previous = _previous; + } + } else { + _all_graphs = _next; + if (_next) { + _next->_previous = nullptr; + } + } + all_unlock(); + + for (auto subgraph : _subgraphs) { + subgraph->graph_destroyed(); + } + + if (_keys) { + delete _keys; + } +} + +#pragma mark - Context + +bool Graph::is_context_updating(uint64_t context_id) { + for (auto update_stack = current_update(); update_stack != nullptr; update_stack = update_stack.get()->previous()) { + for (auto frame = update_stack.get()->frames().rbegin(), end = update_stack.get()->frames().rend(); + frame != end; ++frame) { + auto subgraph = AttributeID(frame->attribute).subgraph(); + if (subgraph && subgraph->context_id() == context_id) { + return true; + } + } + } +} + +Graph::Context *Graph::main_context() const { + struct Info { + Context *context; + uint64_t context_id; + }; + Info info = {nullptr, UINT64_MAX}; + _contexts_by_id.for_each( + [](const uint64_t context_id, Context *const context, void *info_ref) { + auto typed_info_ref = (std::pair *)info_ref; + if (context_id < ((Info *)info_ref)->context_id) { + ((Info *)info_ref)->context = context; + ((Info *)info_ref)->context_id = context_id; + } + }, + &info); + return info.context; +} + +#pragma mark - Invalidating + +Graph::without_invalidating::without_invalidating(Graph *graph) { + _graph = graph; + _graph_old_batch_invalidate_subgraphs = graph->_deferring_subgraph_invalidation; + graph->_deferring_subgraph_invalidation = true; +} + +Graph::without_invalidating::~without_invalidating() { + if (_graph && _graph_old_batch_invalidate_subgraphs == false) { + _graph->_deferring_subgraph_invalidation = false; + _graph->invalidate_subgraphs(); + } +} + +#pragma mark - Context + +#pragma mark - Subgraphs + +void Graph::add_subgraph(Subgraph &subgraph) { + _subgraphs.push_back(&subgraph); + + _num_subgraphs += 1; + _num_subgraphs_created += 1; +} + +void Graph::remove_subgraph(Subgraph &subgraph) { + auto iter = std::remove(_subgraphs.begin(), _subgraphs.end(), &subgraph); + _subgraphs.erase(iter); + + if (auto map = _tree_data_elements_by_subgraph.get()) { + auto iter = map->find(&subgraph); + if (iter != map->end()) { + map->erase(iter); + } + } + + if (subgraph.other_state() & Subgraph::CacheState::Option1) { + subgraph.set_other_state(subgraph.other_state() & ~Subgraph::CacheState::Option1); // added to graph + + auto iter = std::remove(_subgraphs_with_cached_nodes.begin(), _subgraphs_with_cached_nodes.end(), &subgraph); + _subgraphs_with_cached_nodes.erase(iter); + } + + _num_subgraphs -= 1; +} + +void Graph::invalidate_subgraphs() { + if (_deferring_subgraph_invalidation) { + return; + } + + if (_main_handler == nullptr) { + auto iter = _subgraphs_with_cached_nodes.begin(), end = _subgraphs_with_cached_nodes.end(); + while (iter != end) { + auto subgraph = *iter; + subgraph->set_other_state(subgraph->other_state() | Subgraph::CacheState::Option2); + subgraph->cache_collect(); + uint8_t cache_state = subgraph->other_state(); + subgraph->set_other_state(subgraph->other_state() & ~Subgraph::CacheState::Option2); + + if ((cache_state & Subgraph::CacheState::Option1) == 0) { + end = _subgraphs_with_cached_nodes.erase(iter); + } else { + ++iter; + } + } + while (!_invalidated_subgraphs.empty()) { + auto subgraph = _invalidated_subgraphs.back(); + subgraph->invalidate_now(*this); + _invalidated_subgraphs.pop_back(); + } + } +} + +#pragma mark - Updates + +util::tagged_ptr Graph::current_update() { + return util::tagged_ptr((UpdateStack *)pthread_getspecific(_current_update_key)); +} + +void Graph::set_current_update(util::tagged_ptr current_update) { + pthread_setspecific(_current_update_key, (void *)current_update.value()); +} + +bool Graph::thread_is_updating() { + for (auto update_stack = current_update(); update_stack != nullptr; update_stack = update_stack.get()->previous()) { + if (update_stack.get()->graph() == this) { + return true; + } + } + return false; +} + +void Graph::call_update() { + while (_needs_update) { + _needs_update = false; + _contexts_by_id.for_each( + [](uint64_t context_id, Context *graph_context, void *closure_context) { graph_context->call_update(); }, + nullptr); + } +} + +void Graph::reset_update(data::ptr node) { + for (auto update_stack = current_update(); update_stack != nullptr; update_stack = update_stack.get()->previous()) { + for (auto frame : update_stack.get()->frames()) { + if (frame.attribute == node) { + frame.num_pushed_inputs = 0; + } + } + } +} + +void Graph::collect_stack(vector, 0, uint64_t> &nodes) { + for (auto update_stack = current_update(); update_stack != nullptr; update_stack = update_stack.get()->previous()) { + auto &frames = update_stack.get()->frames(); + for (auto iter = frames.rbegin(), end = frames.rend(); iter != end; ++iter) { + nodes.push_back(iter->attribute); + } + } +} + +void Graph::with_update(data::ptr node, ClosureFunctionVV body) { + class scoped_update { + private: + UpdateStack _base; + + public: + scoped_update(Graph *graph, uint8_t options, data::ptr node) : _base(graph, options) { + _base.frames().push_back({node, node->state().is_pending()}); + }; + ~scoped_update() { _base.frames().pop_back(); } + }; + + scoped_update update = scoped_update(this, 0, node); + body(); + // ~scoped_update called +} + +void Graph::without_update(ClosureFunctionVV body) { + // TODO: use RAII pattern here too? + // TODO: is tag here update not active or is paused? + + auto previous_update = current_update(); + AG::Graph::set_current_update(previous_update != nullptr ? previous_update.with_tag(true) : nullptr); + body(); + AG::Graph::set_current_update(previous_update); +} + +void Graph::with_main_handler(ClosureFunctionVV body, MainHandler _Nullable main_handler, + const void *main_handler_context) { + + auto old_main_handler = _main_handler; + auto old_main_handler_context = _main_handler_context; + + _main_handler = main_handler; + _main_handler_context = main_handler_context; + + body(); + + _main_handler = old_main_handler; + _main_handler_context = old_main_handler_context; +} + +void Graph::call_main_handler(void *context, void (*body)(void *)) { + assert(_main_handler); + + struct MainTrampoline { + Graph *graph; + pthread_t thread; + void *context; + void (*handler)(void *); + + static void thunk(const void *arg) { + auto trampoline = reinterpret_cast(arg); + trampoline->handler(trampoline->context); + }; + }; + + auto current_update_thread = _current_update_thread; + auto main_handler = _main_handler; + auto main_handler_context = _main_handler_context; + + _current_update_thread = 0; + _main_handler = nullptr; + _main_handler_context = nullptr; + + MainTrampoline trampoline = {this, current_update_thread, context, body}; + main_handler(main_handler_context, MainTrampoline::thunk, &trampoline); + + _main_handler = main_handler; + _main_handler_context = main_handler_context; + _current_update_thread = current_update_thread; +} + +bool Graph::passed_deadline() { + if (_deadline == -1) { + return false; + } + return passed_deadline_slow(); +} + +bool Graph::passed_deadline_slow() { + if (_deadline == 0) { + return true; + } + + uint64_t time = mach_absolute_time(); + if (time < _deadline) { + return false; + } + + foreach_trace([](Trace &trace) { trace.passed_deadline(); }); + _deadline = 0; + + return true; +} + +#pragma mark - Attributes + +const AttributeType &Graph::attribute_type(uint32_t type_id) const { return *_types[type_id]; } + +const AttributeType *Graph::attribute_ref(data::ptr node, const void *_Nullable *_Nullable self_out) const { + auto &type = attribute_type(node->type_id()); + if (self_out) { + *self_out = node->get_self(type); + } + return &type; } -const AttributeType &Graph::attribute_type(uint32_t type_id) const { - // TODO: Not implemented - throw; +void Graph::attribute_modify(data::ptr node, const swift::metadata &metadata, + ClosureFunctionPV modify, bool invalidating) { + if (!node->state().is_self_initialized()) { + precondition_failure("no self data: %u", node); + } + + auto type = attribute_type(node->type_id()); + if (&type.self_metadata() != &metadata) { + precondition_failure("self type mismatch: %u", node); + } + + foreach_trace([&node](Trace &trace) { trace.begin_modify(node); }); + + void *body = node->get_self(type); + modify(body); // TODO: make context first parameter... + + foreach_trace([&node](Trace &trace) { trace.end_modify(node); }); + + if (invalidating) { + node->flags().set_self_modified(true); + mark_pending(node, node.get()); + } +} + +data::ptr Graph::add_attribute(Subgraph &subgraph, uint32_t type_id, const void *body, const void *value) { + const AttributeType &type = attribute_type(type_id); + + const void *value_source = nullptr; + if (type.use_graph_as_initial_value()) { + value_source = this; + } + if (value != nullptr || type.value_metadata().vw_size() != 0) { + value_source = value; + } + + void *buffer = nullptr; + size_t size = type.self_metadata().vw_size(); + size_t alignment_mask = type.self_metadata().getValueWitnesses()->getAlignmentMask(); + if (!type.self_metadata().getValueWitnesses()->isBitwiseTakable() || type.self_metadata().vw_size() > 0x80) { + size = sizeof(void *); + alignment_mask = 7; + buffer = subgraph.alloc_persistent(size); + } + + size_t total_size = ((sizeof(Node) + alignment_mask) & ~alignment_mask) + size; + + data::ptr node; + if (total_size > 0x10) { + node = subgraph.alloc_bytes_recycle(uint32_t(total_size), uint32_t(alignment_mask | 3)).unsafe_cast(); + } else { + node = (data::ptr)subgraph.alloc_bytes(uint32_t(total_size), uint32_t(alignment_mask | 3)) + .unsafe_cast(); + } + bool main_thread = type.main_thread(); + *node = Node(Node::State().with_main_thread(main_thread).with_main_thread_only(main_thread), type_id, + Node::Flags(0x20)); + + if (type_id >= 0x100000) { + precondition_failure("too many node types allocated"); + } + + void *self = (uint8_t *)node.get() + type.attribute_offset(); + + node->set_state(node->state().with_self_initialized(true)); + if (node->state().is_main_thread_only() && !type.value_metadata().getValueWitnesses()->isPOD()) { + node->flags().set_unknown0x20(!type.unknown_0x20()); // toggle + } else { + node->flags().set_unknown0x20(false); + } + if (buffer != nullptr) { + node->flags().set_has_indirect_self(true); + *(void **)self = buffer; + } + + if (!type.value_metadata().getValueWitnesses()->isBitwiseTakable() || type.value_metadata().vw_size() > 0x80) { + node->flags().set_has_indirect_value(true); + } + + _num_nodes += 1; + _num_nodes_created += 1; + + if (type.self_metadata().vw_size() != 0) { + void *self_dest = self; + if (node->flags().has_indirect_self()) { + self_dest = *(void **)self_dest; + } + type.self_metadata().vw_initializeWithCopy((swift::opaque_value *)self_dest, (swift::opaque_value *)body); + } + + if (value_source != nullptr) { + value_set_internal(node, *node.get(), value_source, type.value_metadata()); + } else { + node->set_state(node->state().with_dirty(true).with_pending(true)); + subgraph.add_dirty_flags(node->subgraph_flags()); + } + + subgraph.add_node(node); + return node; +} + +Graph::UpdateStatus Graph::update_attribute(AttributeID attribute, uint8_t options) { + if (!(options & 1) && _needs_update) { + if (!thread_is_updating()) { + call_update(); + } + } + + Node &node = attribute.to_node(); + if (node.state().is_value_initialized() && !node.state().is_dirty()) { + return UpdateStatus::NoChange; + } + + _update_count += 1; + if (node.state().is_main_thread()) { + _update_on_main_count += 1; + } + + UpdateStack update_stack = UpdateStack(this, options); + + foreach_trace([&update_stack, &attribute, &options](Trace &trace) { + trace.begin_update(update_stack, attribute.to_ptr(), options); + }); + + UpdateStatus status = UpdateStatus::Changed; + if (update_stack.push(attribute.to_ptr(), node, false, (options & 1) == 0)) { + + status = update_stack.update(); + if (status == UpdateStatus::NeedsCallMainHandler) { + std::pair context = {&update_stack, UpdateStatus::NeedsCallMainHandler}; + call_main_handler(&context, [](void *void_context) { + auto inner_context = reinterpret_cast *>(void_context); + util::tagged_ptr previous = Graph::current_update(); + inner_context->second = inner_context->first->update(); + Graph::set_current_update(previous); + }); + status = context.second; + + _update_on_main_count += 1; + } + } + + foreach_trace([&update_stack, &attribute, &status](Trace &trace) { + trace.end_update(update_stack, attribute.to_ptr(), status); + }); + + // ~UpdateStatus called +} + +void Graph::update_main_refs(AttributeID attribute) { + if (attribute.is_nil()) { + return; + } + + auto output_edge_arrays = vector(); + auto update_unknown0x20 = [this, &output_edge_arrays](const AttributeID &attribute) { + if (attribute.is_nil()) { + return; + } + + if (attribute.is_direct()) { + Node &node = attribute.to_node(); + const AttributeType &type = this->attribute_type(node.type_id()); + + bool new_unknown0x20 = false; + if (type.value_metadata().getValueWitnesses()->isPOD() || type.unknown_0x20()) { + new_unknown0x20 = false; + } else { + if (node.state().is_main_thread_only()) { + new_unknown0x20 = true; + } else { + new_unknown0x20 = + std::any_of(node.inputs().begin(), node.inputs().end(), [](auto input_edge) -> bool { + auto resolved = input_edge.value.resolve(TraversalOptions::EvaluateWeakReferences); + if (resolved.attribute().is_direct() && + resolved.attribute().to_node().flags().unknown0x20()) { + return true; + } + }); + } + } + if (node.flags().unknown0x20() != new_unknown0x20) { + node.flags().set_unknown0x20(new_unknown0x20); + output_edge_arrays.push_back({ + &node.outputs().front(), + node.outputs().size(), + }); + } + } else if (attribute.is_indirect() && attribute.to_indirect_node().is_mutable()) { + MutableIndirectNode &node = attribute.to_indirect_node().to_mutable(); + output_edge_arrays.push_back({ + &node.outputs().front(), + node.outputs().size(), + }); + } + }; + + update_unknown0x20(attribute); + + while (!output_edge_arrays.empty()) { + ConstOutputEdgeArrayRef array = output_edge_arrays.back(); + output_edge_arrays.pop_back(); + + for (auto output_edge = array.rbegin(), output_edge_end = array.rend(); output_edge != output_edge_end; + ++output_edge) { + update_unknown0x20(output_edge->value); + } + } +} + +void Graph::remove_node(data::ptr node) { + if (node->state().is_updating()) { + precondition_failure("deleting updating attribute: %u\n", node); + } + + for (auto input_edge : node->inputs()) { + // TODO: check conversion is necessary here... + this->remove_removed_input(AttributeID(node), input_edge.value); + } + for (auto output_edge : node->outputs()) { + this->remove_removed_output(AttributeID(node), output_edge.value, false); + } + + if (_profile_data != nullptr) { + _profile_data->remove_node(node, node->type_id()); + } +} + +bool Graph::breadth_first_search(AttributeID attribute, SearchOptions options, + ClosureFunctionAB predicate) const { + auto resolved = attribute.resolve(TraversalOptions::SkipMutableReference); + if (!resolved.attribute().has_value()) { + return false; + } + + auto seen = std::set(); + + auto queue = std::deque(); + queue.push_back(resolved.attribute()); + + while (!queue.empty()) { + AttributeID candidate = queue.front(); + queue.pop_front(); + + if (candidate.is_nil()) { + continue; + } + + if (candidate.is_direct() && predicate(candidate)) { + return true; + } + + if (options & SearchOptions::SearchInputs) { + if (candidate.is_direct()) { + for (auto input_edge : candidate.to_node().inputs()) { + auto input = input_edge.value.resolve(TraversalOptions::SkipMutableReference).attribute(); + + if (seen.contains(input)) { + continue; + } + if (options & SearchOptions::TraverseGraphContexts || + candidate.subgraph()->context_id() == input.subgraph()->context_id()) { + seen.insert(input); + queue.push_back(input); + } + } + } else if (candidate.is_indirect()) { + // TODO: inputs view on IndirectNode with source? + AttributeID source = candidate.to_indirect_node().source().attribute(); + source = source.resolve(TraversalOptions::SkipMutableReference).attribute(); + + if (!seen.contains(source)) { + if (options & SearchOptions::TraverseGraphContexts || + candidate.subgraph()->context_id() == source.subgraph()->context_id()) { + seen.insert(source); + queue.push_back(source); + } + } + } + } + if (options & SearchOptions::SearchOutputs) { + if (candidate.is_direct()) { + for (auto output_edge : candidate.to_node().outputs()) { + if (seen.contains(output_edge.value)) { + continue; + } + if (options & SearchOptions::TraverseGraphContexts || + candidate.subgraph()->context_id() == output_edge.value.subgraph()->context_id()) { + seen.insert(output_edge.value); + queue.push_back(output_edge.value); + } + } + } else if (candidate.is_indirect()) { + // TODO: how to know it is mutable? + for (auto output_edge : candidate.to_indirect_node().to_mutable().outputs()) { + if (seen.contains(output_edge.value)) { + continue; + } + if (options & SearchOptions::TraverseGraphContexts || + candidate.subgraph()->context_id() == output_edge.value.subgraph()->context_id()) { + seen.insert(output_edge.value); + queue.push_back(output_edge.value); + } + } + } + } + } + + return false; } -const AttributeType &Graph::attribute_ref(data::ptr attribute, const void *_Nullable *_Nullable ref_out) const { - auto &type = attribute_type(attribute->type_id()); - if (ref_out) { - void *self = ((char *)attribute.get() + type.attribute_offset()); - if (attribute->has_indirect_self()) { - self = *(void **)self; +#pragma mark - Indirect attributes + +data::ptr Graph::add_indirect_attribute(Subgraph &subgraph, AttributeID attribute, uint32_t offset, + std::optional size, bool is_mutable) { + if (subgraph.graph() != attribute.subgraph()->graph()) { + precondition_failure("attribute references can't cross graph namespaces"); + } + + auto offset_attribute = attribute.resolve(TraversalOptions::SkipMutableReference); + attribute = offset_attribute.attribute(); + if (__builtin_add_overflow(offset, offset_attribute.offset(), &offset) || + offset + offset_attribute.offset() > 0x3ffffffe) { + precondition_failure("indirect attribute overflowed: %lu + %lu", offset, offset_attribute.offset()); + } + + if (size.has_value()) { + auto attribute_size = attribute.size(); + if (attribute_size.has_value() && attribute_size.value() < offset + size.value()) { + // TODO: check args + precondition_failure("invalid size for indirect attribute: %d vs %u", attribute_size.has_value(), + offset_attribute.offset()); } - *ref_out = self; } - return type; + + if (is_mutable) { + auto indirect_node = subgraph.alloc_bytes(sizeof(MutableIndirectNode), 3).unsafe_cast(); + + // TODO: check accessing zone_id directly or through raw_page_seed + // check references of raw_page_seed in ghidra + uint32_t zone_id = attribute.has_value() ? attribute.subgraph()->info().zone_id() : 0; + auto source = WeakAttributeID(attribute, zone_id); + bool traverses_contexts = subgraph.context_id() != attribute.subgraph()->context_id(); + uint16_t node_size = size.has_value() && size.value() <= 0xfffe ? uint16_t(size.value()) : 0xffff; + *indirect_node = MutableIndirectNode(source, traverses_contexts, offset, node_size, source, offset); + + add_input_dependencies(AttributeID(indirect_node).with_kind(AttributeID::Kind::Indirect), attribute); + subgraph.add_indirect(indirect_node.unsafe_cast(), true); + return indirect_node.unsafe_cast(); + } else { + auto indirect_node = subgraph.alloc_bytes_recycle(sizeof(Node), 3).unsafe_cast(); + + uint32_t zone_id = attribute.has_value() ? attribute.subgraph()->info().zone_id() : 0; + auto source = WeakAttributeID(attribute, zone_id); + bool traverses_contexts = subgraph.context_id() != attribute.subgraph()->context_id(); + uint16_t node_size = size.has_value() && size.value() <= 0xfffe ? uint16_t(size.value()) : 0xffff; + *indirect_node = IndirectNode(source, traverses_contexts, offset, node_size); + + subgraph.add_indirect(indirect_node, &subgraph != attribute.subgraph()); + return indirect_node; + } } -void Graph::did_allocate_node_value(size_t size) { - // TODO: Not implemented +void Graph::remove_indirect_node(data::ptr indirect_node_ptr) { + if (indirect_node_ptr->is_mutable()) { + AttributeID attribute = AttributeID(indirect_node_ptr); + + remove_removed_input(attribute, indirect_node_ptr->source().attribute()); + AttributeID dependency = indirect_node_ptr->to_mutable().dependency(); + if (dependency != 0) { // TODO: == nullptr operator... + remove_removed_input(attribute, dependency); + } + for (auto output_edge : indirect_node_ptr->to_mutable().outputs()) { + remove_removed_output(attribute, output_edge.value, false); + } + return; + } + + while (true) { + if (indirect_node_ptr->source().expired()) { + return; + } + + AttributeID source = indirect_node_ptr->source().attribute(); + if (source.subgraph()->validation_state() == Subgraph::ValidationState::Invalidated) { + break; + } + + if (source.is_direct()) { + auto removed_outputs = vector(); + for (auto output_edge : source.to_node().outputs()) { + if (remove_removed_output(AttributeID(indirect_node_ptr), output_edge.value, false)) { + removed_outputs.push_back(output_edge.value); + } + } + for (auto output : removed_outputs) { + remove_removed_input(output, AttributeID(indirect_node_ptr)); + // remove_removed_input(output, attribute); + } + break; + } else if (source.is_indirect()) { + if (source.to_indirect_node().is_mutable()) { + auto removed_outputs = vector(); + for (auto output_edge : source.to_indirect_node().to_mutable().outputs()) { + if (remove_removed_output(AttributeID(indirect_node_ptr), output_edge.value, false)) { + removed_outputs.push_back(output_edge.value); + } + } + for (auto output : removed_outputs) { + remove_removed_input(output, AttributeID(indirect_node_ptr)); + } + break; + } else { + indirect_node_ptr = source.to_ptr(); + } + } else { + break; + } + } } -void Graph::did_destroy_node_value(size_t size) { - // TODO: Not implemented +void Graph::indirect_attribute_set(data::ptr attribute, AttributeID source) { + if (!attribute->is_mutable()) { + precondition_failure("not an indirect attribute: %u", attribute); + } + if (AttributeID(attribute).subgraph()->graph() != source.subgraph()->graph()) { + precondition_failure("attribute references can't cross graph namespaces"); + } + + foreach_trace([&attribute, &source](Trace &trace) { trace.set_source(attribute, source); }); + + OffsetAttributeID resolved_source = source.resolve(TraversalOptions::SkipMutableReference); + source = resolved_source.attribute(); + uint32_t offset = resolved_source.offset(); + + AttributeID previous_source = attribute->source().attribute(); + if (resolved_source.attribute() == previous_source) { + if (resolved_source.offset() == attribute.offset()) { + return; + } + } else { + remove_input_dependencies(AttributeID(attribute), previous_source); + } + + // common method with this and _reset from here? + + // TODO: check zone id + attribute->modify(WeakAttributeID(resolved_source.attribute(), 0), resolved_source.offset()); + attribute->set_traverses_contexts(AttributeID(attribute).subgraph()->context_id() != + resolved_source.attribute().subgraph()->context_id()); + + if (resolved_source.attribute() != previous_source) { + add_input_dependencies(AttributeID(attribute), resolved_source.attribute()); + } + + mark_changed(AttributeID(attribute), nullptr, 0, 0, 0); + propagate_dirty(AttributeID(attribute)); } -void Graph::update_attribute(AttributeID attribute, bool option) { - // TODO: Not implemented +bool Graph::indirect_attribute_reset(data::ptr attribute, bool non_nil) { + if (!attribute->is_mutable()) { + precondition_failure("not an indirect attribute: %u", attribute); + } + + auto initial_source = attribute->to_mutable().initial_source(); + if (!initial_source.attribute().has_value() && non_nil) { + return false; + } + + WeakAttributeID new_source = {AttributeIDNil, 0}; + uint32_t new_offset = 0; + if (!initial_source.expired()) { + new_source = initial_source; + new_offset = attribute->to_mutable().initial_offset(); + } + + // common method with this and _set from here? + + AttributeID old_source_or_nil = attribute->source().evaluate(); + AttributeID new_source_or_nil = new_source.evaluate(); + + foreach_trace([&attribute, &new_source_or_nil](Trace &trace) { trace.set_source(attribute, new_source_or_nil); }); + + if (old_source_or_nil != new_source_or_nil) { + remove_input_dependencies(AttributeID(attribute), old_source_or_nil); + } + + attribute->modify(new_source, new_offset); + attribute->set_traverses_contexts(AttributeID(attribute).subgraph()->context_id() != + new_source_or_nil.subgraph()->context_id()); + + if (old_source_or_nil != new_source_or_nil) { + add_input_dependencies(AttributeID(attribute), new_source_or_nil); + } + + mark_changed(AttributeID(attribute), nullptr, 0, 0, 0); + propagate_dirty(AttributeID(attribute)); + + return true; +} + +const AttributeID &Graph::indirect_attribute_dependency(data::ptr attribute) { + // Status: Verified + if (!attribute->is_mutable()) { + precondition_failure("not an indirect attribute: %u", attribute); + } + return attribute->to_mutable().dependency(); +} + +void Graph::indirect_attribute_set_dependency(data::ptr attribute, AttributeID dependency) { + // Status: Verified + if (dependency.has_value()) { + if (!dependency.is_direct()) { + precondition_failure("indirect dependencies must be attributes"); + } + if (AttributeID(attribute).subgraph() != dependency.subgraph()) { + precondition_failure("indirect dependencies must share a subgraph with their attribute"); + } + } else { + dependency = AttributeID(); // TODO: check + } + if (!attribute->is_mutable()) { + precondition_failure("not an indirect attribute: %u", attribute); + } + + foreach_trace([&attribute, &dependency](Trace &trace) { trace.set_dependency(attribute, dependency); }); + + AttributeID old_dependency = attribute->to_mutable().dependency(); + if (old_dependency != dependency) { + AttributeID indirect_attribute = AttributeID(attribute).with_kind(AttributeID::Kind::Indirect); + if (old_dependency) { + remove_output_edge(old_dependency.to_ptr(), indirect_attribute); + } + attribute->to_mutable().set_dependency(dependency); + if (dependency) { + add_output_edge(dependency.to_ptr(), indirect_attribute); + if (dependency.to_node().state().is_dirty()) { + propagate_dirty(indirect_attribute); + } + } + } +} + +#pragma mark - Values + +void *Graph::value_ref(AttributeID attribute, uint32_t zone_id, const swift::metadata &value_type, uint8_t *state_out) { + + _version += 1; + + OffsetAttributeID resolved = + attribute.resolve(TraversalOptions::UpdateDependencies | TraversalOptions::ReportIndirectionInOffset | + (zone_id != 0 ? TraversalOptions::EvaluateWeakReferences : TraversalOptions::AssertNotNil)); + + if (zone_id != 0 && (!resolved.attribute().has_value() || !resolved.attribute().is_direct())) { + return nullptr; + } + + Node &node = resolved.attribute().to_node(); + const AttributeType &type = attribute_type(node.type_id()); + + if (!type.use_graph_as_initial_value()) { + + increment_transaction_count_if_needed(); + + uint64_t old_page_seed = 0; + if (zone_id != 0) { + old_page_seed = data::table::shared().raw_page_seed(resolved.attribute().page_ptr()); + } + + UpdateStatus status = update_attribute(resolved.attribute(), 0); + if (status != UpdateStatus::NoChange) { + *state_out = 1; // TODO: check not bool + } + + // check new page seed is same as old and zone is not deleted + if ((old_page_seed >> 0x20) & 0xff) { + uint64_t new_page_seed = data::table::shared().raw_page_seed(resolved.attribute().page_ptr()); + if ((new_page_seed >> 0x20) & 0xff) { + if ((old_page_seed & 0x7fffffff) != (new_page_seed & 0xffffffff)) { + return nullptr; + } + } + } + } + + if (resolved.offset() == 0 && (&type.value_metadata() != &value_type)) { + precondition_failure("invalid value type for attribute: %u (saw %s, expected %s)", resolved.attribute(), + type.value_metadata().name(false), value_type.name(false)); + } + if (!node.state().is_value_initialized()) { + precondition_failure("attribute being read has no value: %u", resolved.attribute()); + } + + void *value = node.get_value(); + if (resolved.offset() != 0) { + value = (uint8_t *)value + (resolved.offset() - 1); + } + return value; +} + +bool Graph::value_set(data::ptr node, const swift::metadata &value_type, const void *value) { + if (!node->inputs().empty() && node->state().is_value_initialized()) { + precondition_failure("can only set initial value of computed attributes: %u", node); + } + + auto update = current_update(); + if (update.tag() == 0 && update.get() != nullptr && update.get()->graph() == this && + (!node->outputs().empty() || node->state().is_updating())) { + precondition_failure("setting value during update: %u", node); + } + + bool changed = value_set_internal(node, *node.get(), value, value_type); + if (changed) { + propagate_dirty(AttributeID(node)); + } + return changed; +} + +bool Graph::value_set_internal(data::ptr node_ptr, Node &node, const void *value_source, + const swift::metadata &value_type) { + foreach_trace([&node_ptr, &value_source](Trace &trace) { trace.set_value(node_ptr, value_source); }); + + AttributeType &type = *_types[node.type_id()]; + if (&type.value_metadata() != &value_type) { + precondition_failure("invalid value type for attribute: %u (saw %s, expected %s)", + type.value_metadata().name(false), value_type.name(false)); + } + + if (node.state().is_value_initialized()) { + // already initialized + void *value_dest = node.get_value(); + + AGComparisonOptions comparison_options = AGComparisonOptions(type.comparison_mode()) | + AGComparisonOptionsCopyOnWrite | AGComparisonOptionsReportFailures; + if (type.layout() == nullptr) { + type.set_layout(LayoutDescriptor::fetch(value_type, comparison_options, 0)); + } + + // TODO: make void * and rename to dest and source + ValueLayout layout = type.layout() == ValueLayoutTrivial ? nullptr : type.layout(); + if (LayoutDescriptor::compare(layout, (const unsigned char *)value_dest, (const unsigned char *)value_source, + value_type.vw_size(), comparison_options)) { + return false; + } + + // TODO: make this inline? + mark_changed(node_ptr, &type, value_dest, value_source); + + value_type.vw_assignWithCopy((swift::opaque_value *)value_dest, (swift::opaque_value *)value_source); + } else { + // not initialized yet + node.allocate_value(*this, *AttributeID(node_ptr).subgraph()); + + // TODO: wrap in initialize_value on Node... + node.set_state(node.state().with_value_initialized(true)); + mark_changed(node_ptr, nullptr, nullptr, nullptr); + + void *value_dest = node.get_value(); + value_type.vw_initializeWithCopy((swift::opaque_value *)value_dest, (swift::opaque_value *)value_source); + } +} + +// Status: Verified +bool Graph::value_exists(data::ptr node) { return node->state().is_value_initialized(); } + +AGValueState Graph::value_state(AttributeID attribute) { + if (!attribute.is_direct()) { + auto resolved_attribute = attribute.resolve(TraversalOptions::AssertNotNil); + attribute = resolved_attribute.attribute(); + } + if (!attribute.is_direct()) { + return 0; + } + + return attribute.to_node().value_state(); +} + +void Graph::value_mark(data::ptr node) { + auto update = current_update(); // TODO: investigate meaning of tags + if (update.tag() == 0 && update.get() != nullptr && update.get()->graph() == this && + (!node->outputs().empty() || node->state().is_updating())) { + precondition_failure("setting value during update: %u", node); + } + + foreach_trace([&node](Trace &trace) { trace.mark_value(node); }); + + AttributeType &type = *_types[node->type_id()]; + if (type.use_graph_as_initial_value()) { + mark_changed(node, nullptr, nullptr, nullptr); + } else { + node->flags().set_self_modified(true); + + if (!node->state().is_dirty()) { + foreach_trace([&node](Trace &trace) { trace.set_dirty(node, true); }); + node->set_state(node->state().with_dirty(true)); + } + if (!node->state().is_pending()) { + foreach_trace([&node](Trace &trace) { trace.set_pending(node, true); }); + node->set_state(node->state().with_pending(true)); + } + if (node->subgraph_flags()) { + Subgraph *subgraph = AttributeID(node).subgraph(); + subgraph->add_dirty_flags(node->subgraph_flags()); + } + } + + propagate_dirty(AttributeID(node)); +} + +void Graph::value_mark_all() { + auto update = current_update(); + if (update.tag() == 0 && update.get() != nullptr) { + precondition_failure("invalidating all values during update"); + } + + for (auto subgraph : _subgraphs) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_nil()) { + break; // TODO: check if this should break out of entire loop + } + if (attribute.is_direct()) { + auto node = attribute.to_node(); + AttributeType &type = *_types[node.type_id()]; + if (!type.use_graph_as_initial_value()) { + node.set_state(node.state().with_dirty(true).with_pending(true)); + subgraph->add_dirty_flags(node.subgraph_flags()); + } + for (auto input_edge : node.inputs()) { + input_edge.set_changed(true); + } + } + } + } + } +} + +void Graph::propagate_dirty(AttributeID attribute) { + if (attribute.is_nil()) { + return; + } + + struct Frame { + data::vector outputs; + Node::State state; + }; + + auto heap = util::InlineHeap<0x2000>(); + auto frames = util::ForwardList(&heap); + + data::vector initial_outputs = {}; + Node::State initial_state = Node::State(0); + if (attribute.is_direct()) { + initial_outputs = attribute.to_node().outputs(); + initial_state = attribute.to_node().state(); + } else if (attribute.is_indirect()) { + // TODO: how to make sure indirect is mutable? + initial_outputs = attribute.to_indirect_node().to_mutable().outputs(); + + OffsetAttributeID source = attribute.to_indirect_node().source().attribute().resolve(TraversalOptions::None); + if (source.attribute().is_direct()) { + initial_state = source.attribute().to_node().state(); + } + } + frames.emplace_front(initial_outputs, initial_state); + + while (!frames.empty()) { + auto outputs = frames.front().outputs; + auto state = frames.front().state; + frames.pop_front(); + + for (auto output_edge = outputs.rbegin(), end = outputs.rend(); output_edge != end; ++output_edge) { + AttributeID output = output_edge->value; + + data::vector dirty_outputs = {}; + Node::State next_state = state; + + if (output.is_direct()) { + Node &output_node = output.to_node(); + + ArrayRef more_outputs = {nullptr, 0}; + if (state.is_main_thread() && !output_node.state().is_main_thread()) { + output_node.set_state(output_node.state().with_main_thread(true)); + dirty_outputs = output_node.outputs(); + } + + next_state = Node::State(output_node.state().data() | state.data()); + + if (!output_node.state().is_dirty()) { + foreach_trace([&output](Trace &trace) { trace.set_dirty(output.to_ptr(), true); }); + + output_node.set_state(output_node.state().with_dirty(true)); + if (auto subgraph = output.subgraph()) { + subgraph->add_dirty_flags(output_node.subgraph_flags()); + } + + dirty_outputs = output_node.outputs(); + + if (output_node.flags().inputs_traverse_contexts() && !output_node.outputs().empty()) { + if (auto output_subgraph = output.subgraph()) { + auto context_id = output_subgraph->context_id(); + if (context_id && + (attribute.subgraph() == nullptr || context_id != attribute.subgraph()->context_id())) { + if (auto context = _contexts_by_id.lookup(context_id, nullptr)) { + if (context->graph_version() != context->graph()._version) { + context->call_invalidation(attribute); + } + } + } + } + } + } + + } else if (output.is_indirect()) { + IndirectNode &output_node = output.to_indirect_node(); + + if (output_node.is_mutable()) { + dirty_outputs = output_node.to_mutable().outputs(); + + if (output_node.traverses_contexts() && !output_node.to_mutable().outputs().empty()) { + if (auto output_subgraph = output.subgraph()) { + auto context_id = output_subgraph->context_id(); + if (context_id && + (attribute.subgraph() == nullptr || context_id != attribute.subgraph()->context_id())) { + if (auto context = _contexts_by_id.lookup(context_id, nullptr)) { + if (context->graph_version() != context->graph()._version) { + context->call_invalidation(attribute); + } + } + } + } + } + } + } + + if (!dirty_outputs.empty()) { + frames.emplace_front(dirty_outputs, next_state); + } + } + } + + for (auto update = current_update(); update != nullptr; update = update.get()->previous()) { + bool stop = false; + auto &frames = update.get()->frames(); + for (auto frame = frames.rbegin(), end = frames.rend(); frame != end; ++frame) { + if (frame->attribute->state().is_main_thread()) { + stop = true; + break; + } + frame->attribute->set_state(frame->attribute->state().with_main_thread(true)); + } + if (stop) { + break; + } + } +} + +#pragma mark - Inputs + +// TODO: inline +void *Graph::input_value_ref(data::ptr node, AttributeID input_attribute, uint32_t subgraph_id, + uint8_t input_flags, const swift::metadata &type, uint8_t *state_out) { + + // TODO: double check this is input_attribute and not node + auto comparator = InputEdge::Comparator( + input_attribute, InputEdge::Flags::Unprefetched | InputEdge::Flags::Unknown1 | InputEdge::Flags::AlwaysEnabled, + input_flags); + + // TODO: index_of_input<0x120> + uint32_t index = index_of_input(*node.get(), comparator); + + if (index < 0) { + // TODO: AVGalueOptions is same as InputEdge::Flags ? + return input_value_ref_slow(node, input_attribute, subgraph_id, input_flags, type, state_out, index); + } + + AG::OffsetAttributeID resolved_input = + input_attribute.resolve(AG::TraversalOptions::UpdateDependencies | AG::TraversalOptions::AssertNotNil); + if (resolved_input.attribute().to_node().state().is_value_initialized() && + !resolved_input.attribute().to_node().state().is_dirty()) { + auto input_edge = node->inputs()[index]; + bool changed = input_edge.is_changed(); + input_edge.set_unknown4(true); // TODO: does this set by reference? + void *value = resolved_input.attribute().to_node().get_value(); + value = (uint8_t *)value + resolved_input.offset(); + + *state_out |= changed ? 1 : 0; + return value; + } + + // TODO: combine with block above, make index signed first though + // TODO: AVGalueOptions is same as InputEdge::Flags ? + return input_value_ref_slow(node, input_attribute, subgraph_id, input_flags, type, state_out, index); +} + +void *Graph::input_value_ref_slow(data::ptr node, AttributeID input_attribute, uint32_t subgraph_id, + uint8_t input_flags, const swift::metadata &type, uint8_t *state_out, + uint32_t index) { + + if ((input_flags >> 1) & 1) { + auto comparator = InputEdge::Comparator(input_attribute, input_flags & 1, + InputEdge::Flags::Unprefetched | InputEdge::Flags::AlwaysEnabled); + index = index_of_input(*node.get(), comparator); + } + + if (index < 0) { + node.assert_valid(); + if (AttributeID(node).subgraph() == nullptr || AttributeID(node).subgraph()->graph() != this) { + precondition_failure("accessing attribute in a different namespace: %u", node); + } + if (!node->state().is_dirty()) { + auto resolved = input_attribute.resolve( + subgraph_id != 0 ? TraversalOptions::UpdateDependencies | TraversalOptions::EvaluateWeakReferences + : TraversalOptions::UpdateDependencies | TraversalOptions::AssertNotNil); + + if (subgraph_id != 0) { + if (!resolved.attribute().has_value() || !resolved.attribute().is_direct()) { + return 0; + } + } + update_attribute(resolved.attribute(), 0); + } + index = add_input(node, input_attribute, subgraph_id != 0, input_flags & 1); + if (index < 0) { + return nullptr; + } + } + + InputEdge &input_edge = node->inputs()[index]; + + input_edge.set_unprefetched(input_edge.is_unprefetched() || (input_flags & 1) ? true : false); + input_edge.set_unknown4(true); + + OffsetAttributeID resolved = input_edge.value.resolve( + subgraph_id != 0 ? TraversalOptions::UpdateDependencies | TraversalOptions::ReportIndirectionInOffset | + TraversalOptions::EvaluateWeakReferences + : TraversalOptions::UpdateDependencies | TraversalOptions::ReportIndirectionInOffset | + TraversalOptions::AssertNotNil); + + if (subgraph_id != 0) { + if (!resolved.attribute().has_value() || !resolved.attribute().is_direct()) { + return nullptr; + } + } + + if (!resolved.attribute().to_node().state().is_value_initialized() || + resolved.attribute().to_node().state().is_dirty()) { + if (subgraph_id != 0) { + auto zone_id_before_update = data::table::shared().raw_page_seed(resolved.attribute().page_ptr()); + + update_attribute(resolved.attribute(), 0); + + if (zone_id_before_update & 0xff00000000) { + auto zone_id_after_update = data::table::shared().raw_page_seed(resolved.attribute().page_ptr()); + if (zone_id_after_update & 0xff00000000) { + if ((zone_id_before_update & 0x7fffffff) != (uint32_t)zone_id_after_update) { + return nullptr; + } + } + } + } else { + update_attribute(resolved.attribute(), 0); + } + } + + if (resolved.attribute().to_node().state().is_pending()) { + *state_out |= 1; + } + if ((input_flags >> 1) & 1 && resolved.attribute().to_node().state().is_self_initialized() && + !type.getValueWitnesses()->isPOD()) { + resolved.attribute().to_node().set_state( + resolved.attribute().to_node().state().with_main_thread_only(true)); // TODO: check + *state_out |= 2; + } + + if (resolved.offset() == 0) { + auto input_type = attribute_type(resolved.attribute().to_node().type_id()).value_metadata(); + if (&input_type != &type) { + precondition_failure("invalid value type for attribute: %u (saw %s, expected %s)", input_edge.value, + input_type.name(false), type.name(false)); + } + } + if (!resolved.attribute().to_node().state().is_value_initialized()) { + precondition_failure("attribute being read has no value: %u", resolved.attribute()); + } + + void *value = resolved.attribute().to_node().get_value(); + if (resolved.offset() != 0) { + value = (uint8_t *)value + resolved.offset() - 1; + } + return value; +} + +void Graph::input_value_add(data::ptr node, AttributeID input_attribute, uint8_t input_flags) { + input_attribute.validate_data_offset(); + + // TODO: check flag and mask are right way around + auto comparator = InputEdge::Comparator(input_attribute, input_flags & 1, + InputEdge::Flags::Unprefetched | InputEdge::Flags::AlwaysEnabled); + auto index = index_of_input(*node.get(), comparator); + if (index >= 0) { + auto input_edge = node->inputs()[index]; + input_edge.set_unknown4(true); + } +} + +uint32_t Graph::add_input(data::ptr node, AttributeID input, bool allow_nil, AGInputOptions options) { + auto resolved = input.resolve(TraversalOptions::EvaluateWeakReferences); + if (!resolved.attribute().has_value()) { + if (allow_nil) { + return -1; + } + precondition_failure("reading from invalid source attribute: %u", input); + } + if (resolved.attribute() == AttributeID(node)) { + precondition_failure("cyclic edge: %u -> %u", resolved.attribute(), node); + } + + foreach_trace([&node, &resolved, &options](Trace &trace) { trace.add_edge(node, resolved.attribute(), options); }); + + auto subgraph = AttributeID(node).subgraph(); + auto graph_context_id = subgraph ? subgraph->context_id() : 0; + + auto input_subgraph = resolved.attribute().subgraph(); + auto input_graph_context_id = input_subgraph ? input_subgraph->context_id() : 0; + + if (graph_context_id != input_graph_context_id) { + node->flags().set_inputs_traverse_contexts(true); + } + + InputEdge new_input_edge = { + resolved.attribute(), + InputEdge::Flags(options & 5), + }; + if (node->state().is_dirty()) { + new_input_edge.set_changed(true); + } + + uint32_t index = -1; + if (node->flags().inputs_unsorted()) { + node->inputs().push_back(subgraph, new_input_edge); + node->flags().set_inputs_unsorted(true); + index = node->inputs().size() - 1; + } else { + auto pos = std::lower_bound(node->inputs().begin(), node->inputs().end(), new_input_edge); + node->inputs().insert(subgraph, pos, new_input_edge); + index = (uint32_t)(pos - node->inputs().begin()); + } + + add_input_dependencies(AttributeID(node), resolved.attribute()); + + if (node->state().is_updating()) { + reset_update(node); + } + if (node->state().is_dirty()) { + foreach_trace([&node, &index](Trace &trace) { trace.set_edge_pending(node, index, true); }); + } + + return index; +} + +void Graph::remove_input(data::ptr node, uint32_t index) { + remove_input_dependencies(AttributeID(node), node->inputs()[index].value); + remove_input_edge(node, *node.get(), index); +} + +void Graph::remove_all_inputs(data::ptr node) { + for (auto index = node->inputs().size() - 1; index >= 0; --index) { + remove_input(node, index); + } + all_inputs_removed(node); +} + +void Graph::add_input_dependencies(AttributeID attribute, AttributeID input) { + auto resolved = input.resolve(TraversalOptions::SkipMutableReference); + if (resolved.attribute().is_direct()) { + add_output_edge(resolved.attribute().to_ptr(), attribute); + } else if (resolved.attribute().is_indirect()) { + assert(resolved.attribute().to_indirect_node().is_mutable()); + add_output_edge(resolved.attribute().to_ptr(), attribute); + } + update_main_refs(attribute); +} + +void Graph::remove_input_dependencies(AttributeID attribute, AttributeID input) { + auto resolved = input.resolve(TraversalOptions::SkipMutableReference); + if (resolved.attribute().is_direct()) { + remove_output_edge(resolved.attribute().to_ptr(), attribute); + } else if (resolved.attribute().is_indirect()) { + assert(resolved.attribute().to_indirect_node().is_mutable()); + remove_output_edge(resolved.attribute().to_ptr(), attribute); + } + update_main_refs(attribute); +} + +void Graph::remove_input_edge(data::ptr node_ptr, Node &node, uint32_t index) { + foreach_trace([&node_ptr, &index](Trace &trace) { trace.remove_edge(node_ptr, index); }); + + node.inputs().erase(node.inputs().begin() + index); + if (node.inputs().size() == 0) { + all_inputs_removed(node_ptr); + } + reset_update(node_ptr); +} + +void Graph::remove_removed_input(AttributeID attribute, AttributeID input) { + auto resolved = input.resolve(TraversalOptions::SkipMutableReference | TraversalOptions::EvaluateWeakReferences); + if (resolved.attribute().subgraph()->validation_state() != Subgraph::ValidationState::Invalidated) { + if (resolved.attribute().is_direct()) { + remove_output_edge(resolved.attribute().to_ptr(), attribute); + } else if (resolved.attribute().is_indirect()) { + assert(resolved.attribute().to_indirect_node().is_mutable()); + remove_output_edge(resolved.attribute().to_ptr(), attribute); + } + } +} + +namespace { + +// TODO: inline, or add to ArrayRef +size_t find_attribute(const AttributeID *attributes, AttributeID search, uint64_t count) { + static_assert(sizeof(wchar_t) == sizeof(AttributeID)); // TODO: check + + const AttributeID *location = (const AttributeID *)wmemchr((const wchar_t *)attributes, search, count); + if (location == nullptr) { + return count; + } + return (location - attributes) / sizeof(AttributeID); +} + +} // namespace + +bool Graph::any_inputs_changed(data::ptr node, const AttributeID *exclude_attributes, + uint64_t exclude_attributes_count) { + for (auto input : node->inputs()) { + input.set_unknown4(true); + if (input.is_changed()) { + if (find_attribute(exclude_attributes, input.value, exclude_attributes_count) == exclude_attributes_count) { + return true; + } + } + } + return false; +} + +void Graph::all_inputs_removed(data::ptr node) { + node->flags().set_inputs_traverse_contexts(false); + node->flags().set_inputs_unsorted(false); + if (!node->state().is_main_thread_only() && !attribute_type(node->type_id()).main_thread()) { + node->set_state(node->state().with_main_thread_only(false)); + } +} + +// TODO: make threshold a template +uint32_t Graph::index_of_input(Node &node, InputEdge::Comparator comparator) { + if (node.inputs().size() > 8) { + return index_of_input_slow(node, comparator); + } + uint32_t index = 0; + for (auto input : node.inputs()) { + if (comparator.match(input)) { + return index; + } + } + return -1; +} + +uint32_t Graph::index_of_input_slow(Node &node, InputEdge::Comparator comparator) { + node.sort_inputs_if_needed(); + InputEdge *search_start = std::find_if(node.inputs().begin(), node.inputs().end(), [&comparator](InputEdge &input) { + return input.value == comparator.attribute; + }); + uint32_t index = uint32_t(search_start - node.inputs().begin()); + for (auto input = search_start, end = node.inputs().end(); input != end; ++input) { + if (comparator.match(*input)) { + return index; + } + index += 1; + } + return -1; +} + +bool Graph::compare_edge_values(InputEdge input_edge, const AttributeType *type, const void *destination_value, + const void *source_value) { + if (type == nullptr) { + return false; + } + + if (!input_edge.value.is_indirect()) { + return false; + } + + auto indirect_node = input_edge.value.to_indirect_node(); + if (!indirect_node.has_size()) { + return false; + } + + auto size = indirect_node.size().value(); + if (size == 0) { + return true; + } + + auto resolved_offset = input_edge.value.resolve(TraversalOptions::None).offset(); + if (resolved_offset == 0 && type->value_metadata().vw_size() == size) { + return false; + } + + AGComparisonOptions options = AGComparisonOptions(type->comparison_mode()) | AGComparisonOptionsCopyOnWrite; + + auto layout = type->layout(); + if (layout == nullptr) { + layout = LayoutDescriptor::fetch(type->value_metadata(), options, 0); + } + if (layout == ValueLayoutTrivial) { + layout = nullptr; + } + + return LayoutDescriptor::compare_partial(layout, (unsigned char *)destination_value + resolved_offset, + (unsigned char *)source_value + resolved_offset, resolved_offset, size, + options); +} + +#pragma mark - Outputs + +void *Graph::output_value_ref(data::ptr node, const swift::metadata &value_type) { + // Status: Verified + if (!node->state().is_updating()) { + precondition_failure("attribute is not evaluating: %u", node); + } + + if (!node->state().is_value_initialized()) { + return nullptr; + } + + const AttributeType &type = attribute_type(node->type_id()); + if (&type.value_metadata() != &value_type) { + precondition_failure("invalid value type for attribute: %u (saw %s, expected %s)", node, + type.value_metadata().name(false), value_type.name(false)); + } + + return node->get_value(); +} + +template <> void Graph::add_output_edge(data::ptr node, AttributeID output) { + node->outputs().push_back(node.page_ptr()->zone, OutputEdge(output)); +} + +template <> void Graph::add_output_edge(data::ptr node, AttributeID output) { + node->outputs().push_back(node.page_ptr()->zone, OutputEdge(output)); +} + +template <> void Graph::remove_output_edge(data::ptr node, AttributeID output) { + for (auto iter = node->outputs().begin(), end = node->outputs().end(); iter != end; ++iter) { + if (iter->value == output) { + node->outputs().erase(iter); + break; + } + } + + if (node->outputs().empty() && node->flags().cacheable()) { + AttributeID(node).subgraph()->cache_insert(node); + } +} + +template <> +void Graph::remove_output_edge(data::ptr node, AttributeID output) { + for (auto iter = node->outputs().begin(), end = node->outputs().end(); iter != end; ++iter) { + if (iter->value == output) { + node->outputs().erase(iter); + break; + } + } +} + +bool Graph::remove_removed_output(AttributeID attribute, AttributeID output, bool flag) { + if (output.subgraph()->validation_state() == Subgraph::ValidationState::Invalidated) { + return false; + } + + if (output.is_direct()) { + auto output_node_ptr = output.to_ptr(); + + uint32_t index = 0; + for (auto input : output_node_ptr->inputs()) { + if (input.value.traverses(attribute, TraversalOptions::SkipMutableReference)) { + remove_input_edge(output_node_ptr, *output_node_ptr.get(), index); + return true; + } + index += 1; + } + return false; + } + + if (!output.is_indirect()) { + return false; + } + + auto indirect_node = output.to_ptr(); + + if (indirect_node->source().attribute() != attribute) { + + // clear dependency + auto dependency = indirect_node->to_mutable().dependency(); + if (dependency && dependency == attribute) { + foreach_trace([&indirect_node](Trace &trace) { trace.set_dependency(indirect_node, AttributeIDNil); }); + indirect_node->to_mutable().set_dependency(AttributeID()); // TODO: 0 or nullptr + return true; + } + + return false; + } + + // reset source + auto initial_source = indirect_node->to_mutable().initial_source(); + WeakAttributeID new_source = {AttributeIDNil, 0}; + uint32_t new_offset = 0; + if (initial_source.attribute().has_value() && !initial_source.expired()) { + new_source = initial_source; + new_offset = indirect_node->to_mutable().initial_offset(); + } + + foreach_trace( + [&indirect_node, &new_source](Trace &trace) { trace.set_source(indirect_node, new_source.attribute()); }); + indirect_node->modify(new_source, new_offset); + + if (new_source.attribute().has_value() && !new_source.expired()) { + add_input_dependencies(output, new_source.attribute()); + } + + return true; +} + +#pragma mark - Marks + +void Graph::mark_changed(data::ptr node, AttributeType *_Nullable type, const void *destination_value, + const void *source_value) { + if (!_traces.empty()) { + mark_changed(AttributeID(node), type, destination_value, source_value, 0); + return; + } + + uint32_t output_index = 0; + for (auto output : node->outputs()) { + if (!output.value.is_direct()) { + mark_changed(AttributeID(node), type, destination_value, source_value, output_index); + return; + } + for (auto input : output.value.to_node().inputs()) { + if (input.value.resolve(TraversalOptions::None).attribute() != AttributeID(node)) { + continue; + } + if (input.is_changed()) { + continue; + } + if (!input.value.is_direct()) { + if (compare_edge_values(input, type, destination_value, source_value)) { + continue; + } + } + input.set_changed(true); + break; + } + output_index += 1; + } + + _change_count += 1; +} + +void Graph::mark_changed(AttributeID attribute, AttributeType *_Nullable type, const void *destination_value, + const void *source_value, uint32_t start_output_index) { + if (attribute.is_nil()) { + return; + } + + // TODO: combine logic with propagate_dirty + struct Frame { + ConstOutputEdgeArrayRef outputs; + AttributeID attribute; + }; + + auto heap = util::InlineHeap<0x2000>(); + auto frames = util::ForwardList(&heap); + + data::vector initial_outputs = {}; + if (attribute.is_direct()) { + initial_outputs = attribute.to_node().outputs(); + } else if (attribute.is_indirect()) { + // TODO: how to make sure indirect is mutable? + initial_outputs = attribute.to_indirect_node().to_mutable().outputs(); + } else { + return; + } + frames.emplace_front(ConstOutputEdgeArrayRef(&initial_outputs.front(), initial_outputs.size()), attribute); + + while (!frames.empty()) { + auto outputs = frames.front().outputs; + auto attribute = frames.front().attribute; + frames.pop_front(); + + for (auto output_edge = outputs.begin() + start_output_index, end = outputs.end(); output_edge != end; + ++output_edge) { + + if (output_edge->value.is_direct()) { + auto inputs = output_edge->value.to_node().inputs(); + for (uint32_t input_index = 0, num_inputs = inputs.size(); input_index < num_inputs; ++input_index) { + auto input_edge = inputs[input_index]; + if (input_edge.value.resolve(TraversalOptions::SkipMutableReference).attribute() != attribute) { + continue; + } + if (input_edge.is_changed()) { + continue; + } + if (!input_edge.value.is_direct()) { + if (compare_edge_values(input_edge, type, destination_value, source_value)) { + continue; + } + } + foreach_trace([&output_edge, &input_index](Trace &trace) { + trace.set_edge_pending(output_edge->value.to_ptr(), input_index, true); + }); + input_edge.set_changed(true); + } + } else if (output_edge->value.is_indirect()) { + if (output_edge->value.to_indirect_node().to_mutable().dependency() != attribute) { + auto mutable_node = output_edge->value.to_indirect_node().to_mutable(); + frames.emplace_front( + ConstOutputEdgeArrayRef(&mutable_node.outputs().front(), mutable_node.outputs().size()), + output_edge->value); + } + } + } + + start_output_index = 0; + } + + _change_count += 1; +} + +void Graph::mark_pending(data::ptr node_ptr, Node *node) { + if (!node->state().is_pending()) { + foreach_trace([&node_ptr](Trace &trace) { trace.set_pending(node_ptr, true); }); + node->set_state(node->state().with_pending(true)); + } + if (!node->state().is_dirty()) { + foreach_trace([&node_ptr](Trace &trace) { trace.set_dirty(node_ptr, true); }); + node->set_state(node->state().with_dirty(true)); + + AGAttributeFlags subgraph_flags = node->subgraph_flags(); + Subgraph *subgraph = AttributeID(node_ptr).subgraph(); + if (subgraph_flags && subgraph != nullptr) { + subgraph->add_dirty_flags(subgraph_flags); + } + + propagate_dirty(AttributeID(node_ptr)); + } +} + +#pragma mark - Interning + +uint32_t Graph::intern_key(const char *key) { + if (_keys == nullptr) { + _keys = new Graph::KeyTable(&_heap); + } + const char *found = nullptr; + uint32_t key_id = _keys->lookup(key, &found); + if (found == nullptr) { + key_id = _keys->insert(key); + } + return key_id; +} + +const char *Graph::key_name(uint32_t key_id) const { + if (_keys != nullptr && key_id < _keys->size()) { + return _keys->get(key_id); + } + AG::precondition_failure("invalid string key id: %u", key_id); +} + +uint32_t Graph::intern_type(const swift::metadata *metadata, ClosureFunctionVP make_type) { + uint32_t type_id = uint32_t(reinterpret_cast(_type_ids_by_metadata.lookup(metadata, nullptr))); + if (type_id) { + return type_id; + } + + AttributeType *type = (AttributeType *)make_type(); + type->update_attribute_offset(); + + static bool prefetch_layouts = []() -> bool { + char *result = getenv("AG_PREFETCH_LAYOUTS"); + if (result) { + return atoi(result) != 0; + } + return false; + }(); + if (prefetch_layouts) { + type->update_layout(); + } + + type_id = _types.size(); + if (type_id >= 0xffffff) { + precondition_failure("overflowed max type id: %u", type_id); + } + _types.push_back(type); + _type_ids_by_metadata.insert(metadata, reinterpret_cast(uintptr_t(type_id))); + + size_t self_size = type->self_metadata().vw_size(); + if (self_size >= 0x2000) { + os_log_info(misc_log(), "large attribute self: %u bytes, %s", uint(self_size), + type->self_metadata().name(false)); + } + + size_t value_size = type->value_metadata().vw_size(); + if (value_size >= 0x2000) { + os_log_info(misc_log(), "large attribute value: %u bytes, %s -> %s", uint(value_size), + type->self_metadata().name(false), type->value_metadata().name(false)); + } + + return type_id; +} + +#pragma mark - Encoding + +void Graph::encode_node(Encoder &encoder, const Node &node, bool flag) { + if (node.type_id()) { + encoder.encode_varint(8); + encoder.encode_varint(node.type_id()); + } + if (flag) { + auto type = attribute_type(node.type_id()); + if (auto callback = type.vt_get_value_description_callback()) { + void *value = node.get_value(); + CFStringRef description = callback(&type, value); + if (description) { + uint64_t length = CFStringGetLength(description); + CFRange range = CFRangeMake(0, length); + uint8_t buffer[1024]; + CFIndex used_buffer_length = 0; + CFStringGetBytes(description, range, kCFStringEncodingUTF8, 0x3f, true, buffer, 1024, + &used_buffer_length); + if (used_buffer_length > 0) { + encoder.encode_varint(0x12); + encoder.encode_data(buffer, used_buffer_length); + } + } + } + } + for (auto input_edge : node.inputs()) { + encoder.encode_varint(0x1a); + encoder.begin_length_delimited(); + if (input_edge.value) { + encoder.encode_varint(8); + encoder.encode_varint(input_edge.value); + } + if (input_edge.is_unprefetched()) { + encoder.encode_varint(0x10); + encoder.encode_varint(input_edge.is_unprefetched()); + } + if (input_edge.is_always_enabled()) { + encoder.encode_varint(0x18); + encoder.encode_varint(input_edge.is_always_enabled()); + } + if (input_edge.is_changed()) { + encoder.encode_varint(0x20); + encoder.encode_varint(input_edge.is_changed()); + } + if (input_edge.is_unknown4()) { + encoder.encode_varint(0x30); + encoder.encode_varint(input_edge.is_unknown4()); + } + encoder.end_length_delimited(); + } + for (auto output_edge : node.outputs()) { + encoder.encode_varint(0x22); + encoder.begin_length_delimited(); + if (output_edge.value) { + encoder.encode_varint(8); + encoder.encode_varint(output_edge.value); + } + encoder.end_length_delimited(); + } + if (node.state().is_dirty()) { + encoder.encode_varint(0x28); + encoder.encode_varint(true); + } + if (node.state().is_pending()) { + encoder.encode_varint(0x30); + encoder.encode_varint(true); + } + if (node.state().is_updating()) { + encoder.encode_varint(0x38); + encoder.encode_varint(true); + } + if (node.subgraph_flags()) { + encoder.encode_varint(0x40); + encoder.encode_varint(node.subgraph_flags()); + } + if (node.state().is_main_thread()) { + encoder.encode_varint(0x48); + encoder.encode_varint(true); + } + if (node.state().is_main_thread_only()) { + encoder.encode_varint(0x50); + encoder.encode_varint(true); + } + if (node.flags().unknown0x20()) { + encoder.encode_varint(0x58); + encoder.encode_varint(node.flags().unknown0x20()); + } + if (node.state().is_value_initialized()) { + encoder.encode_varint(0x60); + encoder.encode_varint(true); + } + if (node.state().is_self_initialized()) { + encoder.encode_varint(0x68); + encoder.encode_varint(true); + } + if (node.flags().cacheable()) { + encoder.encode_varint(0x70); + encoder.encode_varint(true); + } + if (node.flags().self_modified()) { + encoder.encode_varint(0x78); + encoder.encode_varint(true); + } +} + +void Graph::encode_indirect_node(Encoder &encoder, const IndirectNode &indirect_node) { + if (indirect_node.source().attribute()) { + encoder.encode_varint(8); + encoder.encode_varint(indirect_node.source().attribute()); + } + if (indirect_node.source().subgraph_id()) { + encoder.encode_varint(0x10); + encoder.encode_varint(indirect_node.source().subgraph_id()); + } + if (indirect_node.offset()) { + encoder.encode_varint(0x18); + encoder.encode_varint(indirect_node.offset()); + } + auto size = indirect_node.size(); + if (size.has_value() && size.value() != 0) { + encoder.encode_varint(0x20); + encoder.encode_varint(size.value()); + } + if (indirect_node.is_mutable()) { + if (indirect_node.to_mutable().dependency()) { + encoder.encode_varint(0x28); + encoder.encode_varint(indirect_node.to_mutable().dependency()); + } + for (auto output_edge : indirect_node.to_mutable().outputs()) { + encoder.encode_varint(0x32); + encoder.begin_length_delimited(); + if (output_edge.value) { + encoder.encode_varint(8); + encoder.encode_varint(output_edge.value); + } + encoder.end_length_delimited(); + } + } +} + +void Graph::encode_tree(Encoder &encoder, data::ptr tree) { + if (tree->node.has_value()) { + encoder.encode_varint(0x10); + encoder.encode_varint(tree->node); + } + if (tree->flags) { + encoder.encode_varint(0x18); + encoder.encode_varint(tree->flags); + } + for (auto child = tree->first_child; child != nullptr; child = child->next_sibling) { + encoder.encode_varint(0x22); + encoder.begin_length_delimited(); + encode_tree(encoder, child); + encoder.end_length_delimited(); + } + for (auto value = tree->first_value; value != nullptr; value = value->next) { + encoder.encode_varint(0x2a); + encoder.begin_length_delimited(); + if (value->value) { + encoder.encode_varint(0x10); + encoder.encode_varint(value->value); + } + if (value->key_id) { + encoder.encode_varint(0x18); + encoder.encode_varint(value->key_id); + } + if (value->flags) { + encoder.encode_varint(0x20); + encoder.encode_varint(value->flags); + } + encoder.end_length_delimited(); + } + + Subgraph *subgraph = reinterpret_cast(tree.page_ptr()->zone); + // TODO: can combine with tree_node_at_index? + if (auto map = tree_data_elements()) { + auto tree_data_element = map->find(subgraph); + if (tree_data_element != map->end()) { + tree_data_element->second.sort_nodes(); + + auto &nodes = tree_data_element->second.nodes(); + std::pair, data::ptr> *found = + std::find_if(nodes.begin(), nodes.end(), [&tree](auto node) { return node.first == tree; }); + + for (auto node = found; node != nodes.end(); ++node) { + if (node->first != tree) { + break; + } + if (node->second) { + encoder.encode_varint(0x30); + encoder.encode_varint(node->second.offset()); + } + } + } + } +} + +#pragma mark - Tracing + +void Graph::prepare_trace(Trace &trace) { + _contexts_by_id.for_each([](const uint64_t context_id, Context *const context, + void *trace_ref) { ((Trace *)trace_ref)->created(*context); }, + &trace); + + for (auto subgraph : _subgraphs) { + trace.created(*subgraph); + } + for (auto subgraph : _subgraphs) { + for (auto child : subgraph->children()) { + trace.add_child(*subgraph, *child.subgraph()); + } + } + for (auto subgraph : _subgraphs) { + for (uint32_t iteration = 0; iteration < 2; ++iteration) { + for (auto page : subgraph->pages()) { + bool should_break = false; + AttributeIDList list = + iteration == 0 ? (AttributeIDList)AttributeIDList2(page) : (AttributeIDList)AttributeIDList1(page); + for (auto attribute : list) { + if (attribute.is_nil()) { + should_break = true; + break; + } + + if (attribute.is_direct()) { + auto node = attribute.to_ptr(); + trace.added(node); + if (node->state().is_dirty()) { + trace.set_dirty(node, true); + } + if (node->state().is_pending()) { + trace.set_pending(node, true); + } + if (node->state().is_value_initialized()) { + void *value = node->get_value(); + trace.set_value(node, value); + } + } else if (attribute.is_indirect()) { + auto indirect_node = attribute.to_ptr(); + trace.added(indirect_node); + } + } + if (should_break) { + break; + } + } + } + } + for (auto subgraph : _subgraphs) { + for (uint32_t iteration = 0; iteration < 2; ++iteration) { + for (auto page : subgraph->pages()) { + bool should_break = false; + AttributeIDList list = + iteration == 0 ? (AttributeIDList)AttributeIDList2(page) : (AttributeIDList)AttributeIDList1(page); + for (auto attribute : list) { + if (attribute.is_nil()) { + should_break = true; + break; + } + + if (attribute.is_direct()) { + auto node = attribute.to_ptr(); + uint32_t edge_index = 0; + for (auto input_edge : node->inputs()) { + trace.add_edge(node, input_edge.value, + input_edge.is_always_enabled()); // TODO: is last param int or bool? + if (input_edge.is_changed()) { + trace.set_edge_pending(node, edge_index, true); + } + edge_index += 1; + } + } else if (attribute.is_indirect()) { + auto indirect_node = attribute.to_ptr(); + trace.set_source(indirect_node, indirect_node->source().attribute()); + if (indirect_node->is_mutable() && indirect_node->to_mutable().dependency() != 0) { + trace.set_dependency(indirect_node, indirect_node->to_mutable().dependency()); + } + } + } + if (should_break) { + break; + } + } + } + } +} + +void Graph::add_trace(Trace *_Nullable trace) { + if (trace == nullptr) { + return; + } + trace->begin_trace(*this); + _traces.push_back(trace); +} + +void Graph::remove_trace(uint64_t trace_id) { + auto iter = std::remove_if(_traces.begin(), _traces.end(), + [&trace_id](auto trace) -> bool { return trace->trace_id() == trace_id; }); + Trace *trace = *iter; + trace->end_trace(*this); + // destructor? + _traces.erase(iter); +} + +void Graph::start_tracing(uint32_t tracing_flags, std::span subsystems) { + if (tracing_flags & TracingFlags::Enabled && _trace_recorder == nullptr) { + _trace_recorder = new TraceRecorder(this, tracing_flags, subsystems); + if (tracing_flags & TracingFlags::Prepare) { + prepare_trace(*_trace_recorder); + } + add_trace(_trace_recorder); + + // TODO: cleanup block + } +} + +void Graph::stop_tracing() { + if (_trace_recorder) { + remove_trace(_trace_recorder->unique_id()); + _trace_recorder = nullptr; + } +} + +void Graph::sync_tracing() { + foreach_trace([](Trace &trace) { trace.sync_trace(); }); +} + +CFStringRef Graph::copy_trace_path() { + if (_trace_recorder && _trace_recorder->trace_path()) { + return CFStringCreateWithCString(0, _trace_recorder->trace_path(), kCFStringEncodingUTF8); + } else { + return nullptr; + } +} + +void Graph::all_start_tracing(uint32_t tracing_flags, std::span span) { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->start_tracing(tracing_flags, span); + } + all_unlock(); +} + +void Graph::all_stop_tracing() { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->stop_tracing(); + } + all_unlock(); +} + +void Graph::all_sync_tracing() { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->sync_tracing(); + } + all_unlock(); +} + +CFStringRef Graph::all_copy_trace_path() { + CFStringRef result = nullptr; + all_lock(); + if (_all_graphs) { + result = _all_graphs->copy_trace_path(); + } + all_unlock(); + return result; +} + +void Graph::trace_assertion_failure(bool all_stop_tracing, const char *format, ...) { + char *message = nullptr; + + va_list args; + va_start(args, format); + + bool locked = all_try_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->foreach_trace([&format, &args](Trace &trace) { trace.log_message_v(format, args); }); + if (all_stop_tracing) { + graph->stop_tracing(); + } + } + if (locked) { + all_unlock(); + } + + va_end(args); +} + +#pragma mark - Profile + +uint64_t Graph::begin_profile_event(data::ptr node, const char *event_name) { + foreach_trace([this, &node, &event_name](Trace &trace) { trace.begin_event(node, intern_key(event_name)); }); + if (_is_profiling_enabled) { + return mach_absolute_time(); + } + return 0; +} + +void Graph::end_profile_event(data::ptr node, const char *event_name, uint64_t start_time, bool changed) { + auto event_id = intern_key(event_name); + if (_is_profiling_enabled) { + if (_profile_data == nullptr) { + _profile_data.reset(new ProfileData(this)); + } + + auto end_time = mach_absolute_time(); + uint64_t duration = 0; + if (end_time - start_time >= _profile_data->precision()) { + duration = end_time - start_time - _profile_data->precision(); + } + + auto &category = _profile_data.get()->categories().try_emplace(event_id).first->second; + category.add_update(node, duration, changed); + + _profile_data->set_has_unmarked_categories(true); + } + foreach_trace([&node, &event_id](Trace &trace) { trace.end_event(node, event_id); }); +} + +void Graph::add_profile_update(data::ptr node, uint64_t duration, bool changed) { + if (_is_profiling_enabled) { + if (_profile_data == nullptr) { + _profile_data.reset(new ProfileData(this)); + } + + uint64_t effective_duration = 0; + if (duration > _profile_data->precision()) { + effective_duration = duration - _profile_data->precision(); + } + _profile_data->all_events().add_update(node, effective_duration, changed); + _profile_data->set_has_unmarked_categories(true); + } +} + +void Graph::start_profiling(uint32_t profiler_flags) { + _is_profiling_enabled = profiler_flags & 1; // TODO: Make Graph::ProfilerFlags + if ((profiler_flags >> 1) & 1) { + AGAppObserverStartObserving(); + + CFRunLoopRef run_loop = CFRunLoopGetMain(); + if (run_loop) { + CFRunLoopObserverRef observer = CFRunLoopObserverCreate( + 0, kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, 2500000, + [](CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + all_mark_profile("app/runloop"); + }, + nullptr); + if (observer) { + CFRunLoopAddObserver(run_loop, observer, kCFRunLoopCommonModes); // TODO: check mode + } + } + } + if (_is_profiling_enabled && _profile_trace == nullptr) { + _profile_trace = new ProfileTrace(); + add_trace(_profile_trace); + } +} + +void Graph::stop_profiling() { + if (_profile_trace) { + remove_trace(_profile_trace->trace_id()); + _profile_trace = nullptr; + } + _is_profiling_enabled = false; +} + +void Graph::mark_profile(uint32_t event_id, uint64_t time) { + foreach_trace([this, &event_id](Trace &trace) { trace.mark_profile(*this, event_id); }); + + if (_profile_data) { + _profile_data->mark(event_id, time); + } +} + +void Graph::reset_profile() { _profile_data.reset(); } + +void Graph::all_start_profiling(uint32_t profiler_flags) { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->start_profiling(profiler_flags); + } + all_unlock(); +} + +void Graph::all_stop_profiling() { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->stop_profiling(); + } + all_unlock(); +} + +void Graph::all_mark_profile(const char *name) { + uint64_t time = mach_absolute_time(); + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + auto event_id = graph->intern_key(name); + graph->mark_profile(event_id, time); + } + all_unlock(); +} + +void Graph::all_reset_profile() { + all_lock(); + for (auto graph = _all_graphs; graph != nullptr; graph = graph->_next) { + graph->reset_profile(); + } + all_unlock(); } } // namespace AG diff --git a/Sources/ComputeCxx/Graph/Graph.h b/Sources/ComputeCxx/Graph/Graph.h index d1305f6..771f001 100644 --- a/Sources/ComputeCxx/Graph/Graph.h +++ b/Sources/ComputeCxx/Graph/Graph.h @@ -1,27 +1,448 @@ #pragma once +#include #include +#include +#include #include +#include +#include "AGGraph.h" +#include "AGSwiftSupport.h" #include "Attribute/AttributeID.h" +#include "Attribute/Node/Edge.h" +#include "Closure/ClosureFunction.h" +#include "Utilities/HashTable.h" +#include "Utilities/Heap.h" +#include "Utilities/TaggedPointer.h" CF_ASSUME_NONNULL_BEGIN namespace AG { +namespace swift { +class metadata; +} class AttributeType; +class Subgraph; +class Encoder; +class Trace; +class MutableIndirectNode; class Graph { public: - static void trace_assertion_failure(bool all_stop_tracing, const char *format, ...); + class Context; + class KeyTable; + class TraceRecorder; + class UpdateStack; + class UpdateStackRef; + class ProfileData; + class ProfileTrace; + + enum SearchOptions : uint32_t { + SearchInputs = 1 << 0, + SearchOutputs = 1 << 1, + TraverseGraphContexts = 1 << 2, + }; + + enum UpdateStatus : uint32_t { + NoChange = 0, + Changed = 1, + Option2 = 2, + NeedsCallMainHandler = 3, + }; + + struct TreeElement; + struct TreeValue; + class TreeDataElement { + using TreeElementNodePair = std::pair, data::ptr>; + + private: + vector _nodes; + bool _sorted; + + public: + vector &nodes() { return _nodes; }; + void sort_nodes(); + void push_back(TreeElementNodePair pair) { _nodes.push_back(pair); }; + }; + + typedef void (*MainHandler)(const void *_Nullable context AG_SWIFT_CONTEXT, void (*trampoline_thunk)(const void *), + const void *trampoline) AG_SWIFT_CC(swift); + + private: + static Graph *_all_graphs; + static os_unfair_lock _all_graphs_lock; + static pthread_key_t _current_update_key; + + // Graph + Graph *_next; + Graph *_previous; + util::Heap _heap; + + static void all_lock() { os_unfair_lock_lock(&_all_graphs_lock); }; + static bool all_try_lock() { return os_unfair_lock_trylock(&_all_graphs_lock); }; + static void all_unlock() { os_unfair_lock_unlock(&_all_graphs_lock); }; + + // Attribute types + util::UntypedTable _type_ids_by_metadata; + vector _types; + + // Contexts + util::Table _contexts_by_id; + + // Traces + vector _traces; + + // Main thread handler + MainHandler _Nullable _main_handler; + const void *_Nullable _main_handler_context; + + // Metrics + uint64_t _num_nodes = 0; + uint64_t _num_nodes_created = 0; + uint64_t _num_subgraphs = 0; + uint64_t _num_subgraphs_created = 0; + size_t _num_node_value_bytes = 0; + + // Profile + bool _is_profiling_enabled; + std::unique_ptr _profile_data; + ProfileTrace *_Nullable _profile_trace; + + // Trace + TraceRecorder *_trace_recorder; + + // Tree + std::unique_ptr> _tree_data_elements_by_subgraph; + KeyTable *_Nullable _keys; + + // Subgraphs + vector _subgraphs; + vector _subgraphs_with_cached_nodes; + vector _invalidated_subgraphs; + bool _deferring_subgraph_invalidation; + + // Updates + bool _needs_update; + uint32_t _num_contexts; + pthread_t _current_update_thread = 0; + + uint64_t _unique_id; + uint64_t _deadline = UINT64_MAX; + + // Counters + uint64_t _transaction_count = 0; + uint64_t _update_count = 0; + uint64_t _update_on_main_count = 0; + uint64_t _change_count = 0; + uint64_t _version = 0; + + public: + Graph(); + ~Graph(); + + // MARK: Context + + bool is_context_updating(uint64_t context_id); + Context *_Nullable main_context() const; + + static void will_add_to_context(Graph *graph) { graph->_num_contexts += 1; }; + static void did_remove_from_context(Graph *graph) { + graph->_num_contexts -= 1; + if (graph->_num_contexts == 0) { + delete graph; + } + }; + + Context *context_with_id(uint64_t context_id) const { return _contexts_by_id.lookup(context_id, nullptr); } + + // MARK: Subgraphs + + class without_invalidating { + private: + Graph *_graph; + bool _graph_old_batch_invalidate_subgraphs; + + public: + without_invalidating(Graph *graph); + ~without_invalidating(); + }; + + vector &subgraphs() { return _subgraphs; }; + + void add_subgraph(Subgraph &subgraph); // called from constructor of Subgraph + void remove_subgraph(Subgraph &subgraph); + + void will_invalidate_subgraph(Subgraph &subgraph) { _invalidated_subgraphs.push_back(&subgraph); }; + + void invalidate_subgraphs(); + bool is_deferring_subgraph_invalidation() { return _deferring_subgraph_invalidation; }; + void set_deferring_subgraph_invalidation(bool value) { _deferring_subgraph_invalidation = value; }; + + void remove_subgraphs_with_cached_node(Subgraph *subgraph); // overload with iter? + void add_subgraphs_with_cached_node(Subgraph *subgraph) { _subgraphs_with_cached_nodes.push_back(subgraph); } + + // MARK: Updates + + static util::tagged_ptr current_update(); + static void set_current_update(util::tagged_ptr current_update); + + bool thread_is_updating(); + + bool needs_update() { return _needs_update; }; + void set_needs_update(bool needs_update) { _needs_update = needs_update; }; + + void call_update(); + void reset_update(data::ptr node); + + void collect_stack(vector, 0, uint64_t> &nodes); + + void with_update(data::ptr node, ClosureFunctionVV body); + static void without_update(ClosureFunctionVV body); + + MainHandler _Nullable main_handler() { return _main_handler; }; + void with_main_handler(ClosureFunctionVV body, MainHandler _Nullable main_handler, + const void *_Nullable main_handler_context); + void call_main_handler(void *context, void (*body)(void *context)); + + void set_deadline(uint64_t deadline) { _deadline = deadline; }; + bool passed_deadline(); + bool passed_deadline_slow(); + + void increment_transaction_count_if_needed() { + if (!thread_is_updating()) { + _transaction_count += 1; + } + }; + + uint64_t unique_id() const { return _unique_id; }; + + // MARK: Attributes const AttributeType &attribute_type(uint32_t type_id) const; - const AttributeType &attribute_ref(data::ptr attribute, const void *_Nullable *_Nullable ref_out) const; + const AttributeType *attribute_ref(data::ptr node, const void *_Nullable *_Nullable self_out) const; + + void attribute_modify(data::ptr node, const swift::metadata &type, ClosureFunctionPV closure, + bool invalidating); + + data::ptr add_attribute(Subgraph &subgraph, uint32_t type_id, const void *body, const void *_Nullable value); + + UpdateStatus update_attribute(AttributeID attribute, uint8_t options); + void update_main_refs(AttributeID attribute); + + void remove_node(data::ptr node); + + bool breadth_first_search(AttributeID attribute, SearchOptions options, + ClosureFunctionAB predicate) const; + + void did_allocate_node_value(size_t size) { _num_node_value_bytes += size; }; + void did_destroy_node_value(size_t size) { _num_node_value_bytes -= size; }; + + void did_destroy_node() { _num_nodes -= 1; }; + + // MARK: Indirect attributes + + data::ptr add_indirect_attribute(Subgraph &subgraph, AttributeID attribute, uint32_t offset, + std::optional size, bool is_mutable); + void remove_indirect_node(data::ptr node); + + void indirect_attribute_set(data::ptr, AttributeID source); + bool indirect_attribute_reset(data::ptr, bool non_nil); + + const AttributeID &indirect_attribute_dependency(data::ptr indirect_node); + void indirect_attribute_set_dependency(data::ptr indirect_node, AttributeID dependency); + + // MARK: Values + + void *value_ref(AttributeID attribute, uint32_t zone_id, const swift::metadata &value_type, + uint8_t *_Nonnull state_out); + + bool value_set(data::ptr node, const swift::metadata &value_type, const void *value); + bool value_set_internal(data::ptr node_ptr, Node &node, const void *value, const swift::metadata &type); + + bool value_exists(data::ptr node); + AGValueState value_state(AttributeID attribute); + + void value_mark(data::ptr node); + void value_mark_all(); + + void propagate_dirty(AttributeID attribute); + + // MARK: Inputs + + void *_Nullable input_value_ref(data::ptr node, AttributeID input, uint32_t subgraph_id, + uint8_t input_flags, const swift::metadata &type, uint8_t *state_out); + + void *_Nullable input_value_ref_slow(data::ptr node, AttributeID input, uint32_t subgraph_id, + uint8_t input_flags, const swift::metadata &type, uint8_t *state_out, + uint32_t index); + + void input_value_add(data::ptr node, AttributeID input, uint8_t input_edge_flags); + + uint32_t add_input(data::ptr node, AttributeID input, bool allow_nil, AGInputOptions options); + void remove_input(data::ptr node, uint32_t index); + void remove_all_inputs(data::ptr node); + + void add_input_dependencies(AttributeID attribute, AttributeID input); + void remove_input_dependencies(AttributeID attribute, AttributeID input); + + void remove_input_edge(data::ptr node_ptr, Node &node, uint32_t index); + + void remove_removed_input(AttributeID attribute, AttributeID input); + + bool any_inputs_changed(data::ptr node, const AttributeID *exclude_attributes, + uint64_t exclude_attributes_count); + void all_inputs_removed(data::ptr node); + + uint32_t index_of_input(Node &node, InputEdge::Comparator comparator); + uint32_t index_of_input_slow(Node &node, InputEdge::Comparator comparator); + + bool compare_edge_values(InputEdge input_edge, const AttributeType *_Nullable type, const void *destination_value, + const void *source_value); + + // MARK: Outputs + + void *_Nullable output_value_ref(data::ptr node, const swift::metadata &type); + + template void add_output_edge(data::ptr node, AttributeID output); + template <> void add_output_edge(data::ptr node, AttributeID output); + template <> void add_output_edge(data::ptr node, AttributeID output); + + template void remove_output_edge(data::ptr node, AttributeID attribute); + template <> void remove_output_edge(data::ptr node, AttributeID attribute); + template <> + void remove_output_edge(data::ptr node, AttributeID attribute); + + bool remove_removed_output(AttributeID attribute, AttributeID source, bool flag); + + // MARK: Marks + + void mark_changed(data::ptr node, AttributeType *_Nullable type, const void *_Nullable destination_value, + const void *_Nullable source_value); + void mark_changed(AttributeID attribute, AttributeType *_Nullable type, const void *_Nullable destination_value, + const void *_Nullable source_value, uint32_t start_output_index); + + void mark_pending(data::ptr node_ptr, Node *node); + + Graph::TreeDataElement &tree_data_element_for_subgraph(Subgraph *subgraph) { + if (!_tree_data_elements_by_subgraph) { + _tree_data_elements_by_subgraph.reset(new std::unordered_map()); + } + return _tree_data_elements_by_subgraph->try_emplace(subgraph).first->second; + }; + + std::unordered_map *tree_data_elements() { + return _tree_data_elements_by_subgraph.get(); + }; + + // MARK: Intern + + uint32_t intern_key(const char *key); + const char *key_name(uint32_t key_id) const; + + uint32_t intern_type(const swift::metadata *metadata, ClosureFunctionVP make_type); + + // MARK: Encoding + + void encode_node(Encoder &encoder, const Node &node, bool flag); + void encode_indirect_node(Encoder &encoder, const IndirectNode &indirect_node); + + void encode_tree(Encoder &encoder, data::ptr tree); + + // MARK: Counters + + uint64_t num_nodes() const { return _num_nodes; }; + uint64_t num_nodes_created() const { return _num_nodes_created; }; + uint64_t num_subgraphs() const { return _num_subgraphs; }; + uint64_t num_subgraphs_created() const { return _num_subgraphs_created; }; + + uint64_t transaction_count() const { return _transaction_count; }; + uint64_t update_count() const { return _update_count; }; + uint64_t update_on_main_count() const { return _update_on_main_count; }; + uint64_t change_count() const { return _change_count; }; + + // MARK: Tracing + + enum TracingFlags : uint32_t { + Enabled = 1 << 0, + Full = 1 << 1, + Backtrace = 1 << 2, + Prepare = 1 << 3, + Custom = 1 << 4, // skip updates + All = 1 << 5, + }; + + bool is_tracing_active() const { return !_traces.empty(); }; + + void prepare_trace(Trace &trace); + + void add_trace(Trace *_Nullable trace); + void remove_trace(uint64_t trace_id); + + void start_tracing(uint32_t tracing_flags, std::span subsystems); + void stop_tracing(); + void sync_tracing(); + CFStringRef copy_trace_path(); + + const vector &traces() const { return _traces; }; + + template + requires std::invocable + void foreach_trace(T body) { + for (auto trace = _traces.rbegin(), end = _traces.rend(); trace != end; ++trace) { + body(**trace); + } + }; + + static void all_start_tracing(uint32_t tracing_flags, std::span span); + static void all_stop_tracing(); + static void all_sync_tracing(); + static CFStringRef all_copy_trace_path(); + + static void trace_assertion_failure(bool all_stop_tracing, const char *format, ...); + + // MARK: Profile + + bool is_profiling_enabled() const { return _is_profiling_enabled; }; + + uint64_t begin_profile_event(data::ptr node, const char *event_name); + void end_profile_event(data::ptr node, const char *event_name, uint64_t start_time, bool changed); + + void add_profile_update(data::ptr node, uint64_t duration, bool changed); + + void start_profiling(uint32_t profiler_flags); + void stop_profiling(); + void mark_profile(uint32_t event_id, uint64_t time); + void reset_profile(); + + static void all_start_profiling(uint32_t profiler_flags); + static void all_stop_profiling(); + static void all_mark_profile(const char *name); + static void all_reset_profile(); + + // MARK: Printing + + void print(); + void print_attribute(data::ptr node); + + void print_cycle(data::ptr node); + + void print_data(); + static void print_stack(); + + // MARK: Description + + CFStringRef description(data::ptr node); - void did_allocate_node_value(size_t size); - void did_destroy_node_value(size_t size); + static id description(Graph *_Nullable graph, CFDictionaryRef options); + static CFDictionaryRef description_graph(Graph *graph, CFDictionaryRef options); + CFStringRef description_graph_dot(CFDictionaryRef _Nullable options); + CFStringRef description_stack(CFDictionaryRef options); + CFArrayRef description_stack_nodes(CFDictionaryRef options); + CFDictionaryRef description_stack_frame(CFDictionaryRef options); - void update_attribute(AttributeID attribute, bool option); + static void write_to_file(Graph *_Nullable graph, const char *_Nullable filename, bool exclude_values); }; } // namespace AG diff --git a/Sources/ComputeCxx/Graph/KeyTable.cpp b/Sources/ComputeCxx/Graph/KeyTable.cpp new file mode 100644 index 0000000..d6e7c98 --- /dev/null +++ b/Sources/ComputeCxx/Graph/KeyTable.cpp @@ -0,0 +1,36 @@ +#include "KeyTable.h" + +namespace AG { + +Graph::KeyTable::KeyTable(util::Heap *_Nullable heap) + : _table([](const void *v) { return (uint64_t)util::string_hash(reinterpret_cast(v)); }, + [](const void *a, const void *b) { + return std::strcmp(reinterpret_cast(a), reinterpret_cast(b)) == 0; + }, + nullptr, nullptr, heap){}; + +uint32_t Graph::KeyTable::lookup(const char *key, const char *_Nullable *_Nullable found_key) const { + auto result = _table.lookup(key, reinterpret_cast(found_key)); + return (uint32_t)(uintptr_t)result; +} + +uint32_t Graph::KeyTable::size() { + return _keys.size(); +} + +uint32_t Graph::KeyTable::insert(const char *key) { + const char *duplicate = strdup(key); + if (!duplicate) { + precondition_failure("memory allocation failure"); + } + uint32_t key_id = _keys.size(); + _keys.push_back(duplicate); + _table.insert(duplicate, (void *)(uintptr_t)key_id); + return key_id; +} + +const char *Graph::KeyTable::get(uint32_t key_id) { + return _keys[key_id]; +} + +}; // namespace AG diff --git a/Sources/ComputeCxx/Graph/KeyTable.h b/Sources/ComputeCxx/Graph/KeyTable.h new file mode 100644 index 0000000..a428283 --- /dev/null +++ b/Sources/ComputeCxx/Graph/KeyTable.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "Graph.h" +#include "Utilities/HashTable.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Graph::KeyTable { + private: + vector _keys; + util::UntypedTable _table; + + public: + KeyTable(util::Heap *_Nullable heap); + + uint32_t size(); + + uint32_t lookup(const char *key, const char *_Nullable *_Nullable found_key) const; + const char *get(uint32_t key_id); + + uint32_t insert(const char *key); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Profile/AGAppObserver.h b/Sources/ComputeCxx/Graph/Profile/AGAppObserver.h new file mode 100644 index 0000000..23f4cb7 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/AGAppObserver.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +CF_ASSUME_NONNULL_BEGIN + +void AGAppObserverStartObserving(); + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Profile/AGAppObserver.mm b/Sources/ComputeCxx/Graph/Profile/AGAppObserver.mm new file mode 100644 index 0000000..bcc0e69 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/AGAppObserver.mm @@ -0,0 +1,37 @@ +#include "AGAppObserver.h" + +#import + +#include "Graph/Graph.h" + +@interface AGAppObserver : NSObject + ++ (void)foreground:(AG::Graph *)graph; ++ (void)background:(AG::Graph *)graph; + +@end + +@implementation AGAppObserver + ++ (void)foreground:(AG::Graph *)graph { + graph->all_mark_profile("app/foreground"); +} + ++ (void)background:(AG::Graph *)graph { + graph->all_mark_profile("app/background"); +} + +@end + +void AGAppObserverStartObserving() { + if (NSClassFromString(@"UIApplication")) { + [[NSNotificationCenter defaultCenter] addObserver:[AGAppObserver class] + selector:@selector(foreground:) + name:@"UIApplicationWillEnterForegroundNotification" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:[AGAppObserver class] + selector:@selector(background:) + name:@"UIApplicationDidEnterBackgroundNotification" + object:nil]; + } +} diff --git a/Sources/ComputeCxx/Graph/Profile/ProfileData+JSON.mm b/Sources/ComputeCxx/Graph/Profile/ProfileData+JSON.mm new file mode 100644 index 0000000..c01eb52 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/ProfileData+JSON.mm @@ -0,0 +1,61 @@ +#include "ProfileData.h" + +#import + +#include + +#include "Graph/Graph.h" +#include "Time/Time.h" + +namespace AG { + +CFDictionaryRef Graph::ProfileData::json_data(const Data &data) { + NSMutableDictionary *dict = nil; + if (data.update_count) { + if (!dict) { + dict = [NSMutableDictionary dictionary]; + } + dict[@"update_count"] = [NSNumber numberWithUnsignedLong:data.update_count]; + } + if (data.change_count) { + if (!dict) { + dict = [NSMutableDictionary dictionary]; + } + dict[@"change_count"] = [NSNumber numberWithUnsignedLong:data.change_count]; + } + if (data.update_total) { + if (!dict) { + dict = [NSMutableDictionary dictionary]; + } + dict[@"update_total"] = [NSNumber numberWithDouble:absolute_time_to_seconds(data.update_total)]; + } + if (data.changed_total) { + if (!dict) { + dict = [NSMutableDictionary dictionary]; + } + dict[@"changed_total"] = [NSNumber numberWithDouble:absolute_time_to_seconds(data.changed_total)]; + } + return (__bridge CFDictionaryRef)dict; +} + +CFDictionaryRef Graph::ProfileData::json_data(const Item &item, const Graph &graph) { + NSMutableDictionary *json = (__bridge NSMutableDictionary *)json_data(item.data()); + if (!item.marks().empty()) { + NSMutableArray *array = [NSMutableArray array]; + for (auto mark : item.marks()) { + NSMutableDictionary *mark_json = (__bridge NSMutableDictionary *)json_data(mark.data); + if (mark_json) { + mark_json[@"name"] = [NSString stringWithUTF8String:graph.key_name(mark.event_id)]; + mark_json[@"timestamp"] = [NSNumber numberWithDouble:absolute_time_to_seconds(mark.timestamp)]; + [array addObject:mark_json]; + } + } + if (!json) { + json = [NSMutableDictionary dictionary]; + } + json[@"marks"] = array; + } + return (__bridge CFDictionaryRef)json; +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/Profile/ProfileData.cpp b/Sources/ComputeCxx/Graph/Profile/ProfileData.cpp new file mode 100644 index 0000000..38e7e8f --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/ProfileData.cpp @@ -0,0 +1,102 @@ +#include "ProfileData.h" + +#include + +#include "Graph/Graph.h" + +namespace AG { + +#pragma mark - ProfileData::Item + +void Graph::ProfileData::Item::operator+=(const Item &other) { + _data.update_count += other._data.update_count; + _data.change_count += other._data.change_count; + _data.update_total += other._data.update_total; + _data.changed_total += other._data.changed_total; + + Mark *iter = _marks.begin(); + for (auto other_mark : other._marks) { + bool merged = false; + while (iter != _marks.end() && iter->timestamp <= other_mark.timestamp) { + if (iter == &other_mark) { + iter->data.update_count += other_mark.data.update_count; + iter->data.change_count += other_mark.data.change_count; + iter->data.update_total += other_mark.data.update_total; + iter->data.changed_total += other_mark.data.changed_total; + merged = true; + break; + } + iter += 1; + } + if (merged) { + continue; + } + + _marks.insert(iter, other_mark); + iter += 1; + } +} + +void Graph::ProfileData::Item::mark(uint32_t event_id, uint64_t time) { + if (_data.update_count) { + _marks.push_back({ + event_id, + time, + _data, + }); + _data = {0, 0, 0, 0}; + } +} + +#pragma mark - ProfileData::Category + +void Graph::ProfileData::Category::add_update(data::ptr node, uint64_t duration, bool changed) { + data().update_count += 1; + data().update_total += duration; + if (changed) { + data().change_count += 1; + data().changed_total += duration; + } + + Item &item = _items_by_attribute.try_emplace(node).first->second; + + item.data().update_count += 1; + item.data().update_total += duration; + if (changed) { + item.data().change_count += 1; + item.data().changed_total += duration; + } +} + +void Graph::ProfileData::Category::mark(uint32_t event_id, uint64_t time) { + Item::mark(event_id, time); + for (auto &entry : _items_by_attribute) { + entry.second.mark(event_id, time); + } + for (auto &entry : _removed_items_by_type_id) { + entry.second.mark(event_id, time); + } +} + +#pragma mark - ProfileData + +Graph::ProfileData::ProfileData(Graph *graph) { + + uint64_t delta = 0; + uint64_t last = mach_absolute_time(); + for (uint32_t i = 16; i; --i) { + uint64_t current = mach_absolute_time(); + delta += current - last; + last = current; + } + _precision = delta / 16; +} + +void Graph::ProfileData::remove_node(data::ptr node, uint32_t type_id) { + _all_events.remove_node(node, type_id); + for (auto &entry : _categories) { + entry.second.remove_node(node, type_id); + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/Profile/ProfileData.h b/Sources/ComputeCxx/Graph/Profile/ProfileData.h new file mode 100644 index 0000000..1e60a09 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/ProfileData.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +#include "Graph/Graph.h" +#include "Utilities/HashTable.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Graph::ProfileData { + public: + struct Data { + uint64_t update_count; + uint64_t change_count; + uint64_t update_total; + uint64_t changed_total; + }; + + struct Mark { + uint32_t event_id; + uint64_t timestamp; + Data data; + }; + + class Item { + private: + Data _data; + vector _marks; + + public: + Data &data() { return _data; }; + const Data &data() const { return _data; }; + const vector &marks() const { return _marks; }; + + void operator+=(const Item &other); + + void mark(uint32_t event_id, uint64_t time); + }; + + class Category : public Item { + private: + std::unordered_map, Item> _items_by_attribute; + std::unordered_map _removed_items_by_type_id; + + public: + std::unordered_map, Item> &items_by_attribute() { return _items_by_attribute; }; + std::unordered_map &removed_items_by_type_id() { return _removed_items_by_type_id; }; + + void add_update(data::ptr node, uint64_t time, bool flag); + void remove_node(data::ptr node, uint32_t type_id) { + auto found = _items_by_attribute.find(node); + if (found != _items_by_attribute.end()) { + auto &item = _removed_items_by_type_id.try_emplace(type_id).first->second; + item += found->second; + _items_by_attribute.erase(found); + } + }; + + void mark(uint32_t event_id, uint64_t time); + }; + + private: + uint64_t _precision; + Category _all_events; + + std::unordered_map _categories; + bool _has_unmarked_categories; + + public: + ProfileData(Graph *graph); + + uint64_t precision() const { return _precision; }; + + Category &all_events() { return _all_events; }; + std::unordered_map &categories() { return _categories; }; + + bool has_unmarked_categories() const { return _has_unmarked_categories; }; + void set_has_unmarked_categories(bool value) { _has_unmarked_categories = value; }; + + void remove_node(data::ptr node, uint32_t type_id); + + void mark(uint32_t event_id, uint64_t time) { + if (_has_unmarked_categories) { + if (time == 0) { + time = mach_absolute_time(); + } + _all_events.mark(event_id, time); + for (auto &entry : _categories) { + entry.second.mark(event_id, time); // TODO: does this modify in place or a copy? + } + _has_unmarked_categories = false; + } + } + + CFDictionaryRef json_data(const Data &data); + CFDictionaryRef json_data(const Item &item, const Graph &graph); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Profile/ProfileTrace.cpp b/Sources/ComputeCxx/Graph/Profile/ProfileTrace.cpp new file mode 100644 index 0000000..892449a --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/ProfileTrace.cpp @@ -0,0 +1,70 @@ +#include "ProfileTrace.h" + +#include + +#include "Graph/UpdateStack.h" + +namespace AG { + +void Graph::ProfileTrace::begin_update(const Graph::UpdateStack &update_stack, data::ptr node, uint32_t options) { + if (update_stack.graph()->is_profiling_enabled()) { + uint64_t time = mach_absolute_time(); + UpdateData &data = _map.try_emplace(&update_stack).first->second; + data.start_time = time; + data.child_update_stack_duration = 0; + data.update_node_start_time = 0; + } +} + +void Graph::ProfileTrace::end_update(const Graph::UpdateStack &update_stack, data::ptr node, + Graph::UpdateStatus update_status) { + auto found = _map.find(&update_stack); + if (found != _map.end()) { + UpdateData &data = found->second; + _map.erase(found); + + if (auto previous_update_stack = update_stack.previous().get()) { + if (previous_update_stack->graph()->is_profiling_enabled()) { + auto found_previous = _map.find(previous_update_stack); + if (found_previous != _map.end()) { + UpdateData &previous_data = found_previous->second; + uint64_t end_time = previous_update_stack->graph()->is_profiling_enabled() ? mach_absolute_time() : 0; + previous_data.child_update_stack_duration += end_time - data.start_time; + } + } + } + } +} + +void Graph::ProfileTrace::begin_update(data::ptr node) { + auto update = current_update(); + UpdateStack *update_stack = update.tag() ? nullptr : update.get(); + + auto found = _map.find(update_stack); + if (found != _map.end()) { + UpdateData &data = found->second; + data.update_node_start_time = update_stack->graph()->is_profiling_enabled() ? mach_absolute_time() : 0; + } +} + +void Graph::ProfileTrace::end_update(data::ptr node, bool changed) { + auto update = current_update(); + UpdateStack *update_stack = update.tag() ? nullptr : update.get(); + + auto found = _map.find(update_stack); + if (found != _map.end()) { + UpdateData &data = found->second; + if (data.update_node_start_time != 0) { + uint64_t end_time = update_stack->graph()->is_profiling_enabled() ? mach_absolute_time() : 0; + uint64_t duration = 0; + if (end_time - data.update_node_start_time >= data.child_update_stack_duration) { + duration = (end_time - data.update_node_start_time) - data.child_update_stack_duration; + } + data.child_update_stack_duration = 0; + update_stack->graph()->add_profile_update(node, duration, changed); + } + + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/Profile/ProfileTrace.h b/Sources/ComputeCxx/Graph/Profile/ProfileTrace.h new file mode 100644 index 0000000..278ac2b --- /dev/null +++ b/Sources/ComputeCxx/Graph/Profile/ProfileTrace.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "Encoder/Encoder.h" +#include "Trace/Trace.h" +#include "Utilities/HashTable.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Graph::ProfileTrace : public Trace { + private: + struct UpdateData { + uint64_t start_time; + uint64_t child_update_stack_duration; + uint64_t update_node_start_time; + }; + + std::unordered_map _map; + + public: + void begin_update(const Graph::UpdateStack &update_stack, data::ptr node, uint32_t options); + void end_update(const Graph::UpdateStack &update_stack, data::ptr node, Graph::UpdateStatus update_status); + void begin_update(data::ptr node); + void end_update(data::ptr node, bool changed); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/TraceRecorder.cpp b/Sources/ComputeCxx/Graph/TraceRecorder.cpp new file mode 100644 index 0000000..6201c5f --- /dev/null +++ b/Sources/ComputeCxx/Graph/TraceRecorder.cpp @@ -0,0 +1,1398 @@ +#include "TraceRecorder.h" + +#include +#include +#include +#include + +#include "AGGraph.h" +#include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" +#include "Context.h" +#include "KeyTable.h" +#include "Log/Log.h" +#include "Subgraph/Subgraph.h" +#include "Time/Time.h" +#include "UniqueID/AGUniqueID.h" +#include "UpdateStack.h" + +namespace AG { + +namespace { + +uint64_t uuid_hash(const uuid_t key) { return *key; } + +bool uuid_equal(const uuid_t a, const uuid_t b) { return uuid_compare(a, b) == 0; } + +} // namespace + +Graph::TraceRecorder::TraceRecorder(Graph *graph, uint8_t tracing_flags, std::span subsystems) + : _graph(graph), _tracing_flags(tracing_flags), _heap(), + _image_offset_cache(uuid_hash, uuid_equal, nullptr, nullptr, &_heap), _encoder(_delegate, 0x10000) { + _unique_id = AGMakeUniqueID(); + + _delegate = this; + + for (auto subsystem : subsystems) { + _named_event_subsystems.push_back(strdup(subsystem)); + } + + void *array[1] = {(void *)&AGGraphCreate}; + image_offset image_offsets[1]; + backtrace_image_offsets(array, image_offsets, 1); + + uuid_copy(_stack_frame_uuid, image_offsets[0].uuid); +} + +Graph::TraceRecorder::~TraceRecorder() { + _encoder.flush(); + + if (_trace_path) { + free((void *)_trace_path); + _trace_path = nullptr; + } + + for (auto iter = _named_event_subsystems.begin(), end = _named_event_subsystems.end(); iter != end; ++iter) { + if (*iter) { + free((void *)*iter); + } + *iter = nullptr; + } + + void *array[1] = {(void *)&AGGraphCreate}; + image_offset image_offsets[1]; + backtrace_image_offsets(array, image_offsets, 1); + + uuid_copy(_stack_frame_uuid, image_offsets[0].uuid); +} + +void Graph::TraceRecorder::encode_types() { + while (_num_encoded_types < _graph->_types.size()) { + auto attribute_type = _graph->attribute_type(_num_encoded_types); + + _encoder.encode_varint(0x1a); + _encoder.begin_length_delimited(); + + if (_num_encoded_types) { + _encoder.encode_varint(8); + _encoder.encode_varint(_num_encoded_types); + } + auto self_metadata_name = attribute_type.self_metadata().name(false); + auto self_metadata_length = strlen(self_metadata_name); + if (self_metadata_length) { + _encoder.encode_varint(0x12); + _encoder.encode_data((void *)self_metadata_name, self_metadata_length); + } + auto value_metadata_name = attribute_type.value_metadata().name(false); + auto value_metadata_length = strlen(value_metadata_name); + if (value_metadata_length) { + _encoder.encode_varint(0x1a); + _encoder.encode_data((void *)value_metadata_name, value_metadata_length); + } + auto self_size = attribute_type.self_metadata().vw_size(); + if (self_size > 0) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(self_size); + } + auto value_size = attribute_type.value_metadata().vw_size(); + if (value_size > 0) { + _encoder.encode_varint(0x28); + _encoder.encode_varint(value_size); + } + auto flags = attribute_type.flags(); + if (flags) { + _encoder.encode_varint(0x30); + _encoder.encode_varint(flags); + } + + _encoder.end_length_delimited(); + + _num_encoded_types += 1; + } +} + +void Graph::TraceRecorder::encode_keys() { + if (_graph->_keys == nullptr) { + return; + } + while (_num_encoded_keys < _graph->_keys->size()) { + if (auto key_name = _graph->key_name(_num_encoded_keys)) { + _encoder.encode_varint(0x22); + _encoder.begin_length_delimited(); + if (_num_encoded_keys) { + _encoder.encode_varint(8); + _encoder.encode_varint(_num_encoded_keys); + } + auto length = strlen(key_name); + if (length) { + _encoder.encode_varint(0x12); + _encoder.encode_data((void *)key_name, length); + } + _encoder.end_length_delimited(); + } + _num_encoded_keys += 1; + } +} + +void Graph::TraceRecorder::encode_stack() { + auto first_update = current_update(); + if (first_update == 0) { + return; + } + + _encoder.encode_varint(0x2a); + _encoder.begin_length_delimited(); + + for (auto update = first_update; update != nullptr; update = update.get()->previous()) { + auto &frames = update.get()->frames(); + for (auto frame = frames.rbegin(), end = frames.rend(); frame != end; ++frame) { + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + + if (frame->attribute) { + _encoder.encode_varint(8); + _encoder.encode_varint(frame->attribute.offset()); + } + if (frame->needs_update) { + _encoder.encode_varint(0x10); + _encoder.encode_varint(1); + } + if (frame->cyclic) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(1); + } + + _encoder.end_length_delimited(); + } + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::encode_snapshot() { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + encode_types(); + encode_keys(); + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x11); + + field_timestamp(_encoder); + + for (auto subgraph : _graph->subgraphs()) { + if (subgraph->is_valid()) { + _encoder.encode_varint(0x12); + _encoder.begin_length_delimited(); + subgraph->encode(_encoder); + _encoder.end_length_delimited(); + } + } + + encode_stack(); + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x12); + field_timestamp(_encoder); + _encoder.end_length_delimited(); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::field_timestamp(Encoder &encoder) { + auto time = current_time(); + if (time != 0.0) { + encoder.encode_varint(0x11); + encoder.encode_fixed64(time); + } +} + +void Graph::TraceRecorder::field_backtrace(Encoder &encoder, uint64_t field_number) { + if ((_tracing_flags & TracingFlags::Backtrace) == 0) { + return; + } + + void *stack_frames_buffer[8]; + auto stack_frames_size = backtrace(stack_frames_buffer, sizeof(stack_frames_buffer)); + + image_offset image_offsets[8]; + backtrace_image_offsets(stack_frames_buffer, image_offsets, stack_frames_size); + + static uint64_t n_stack_frames = []() -> uint64_t { + char *result = getenv("AG_TRACE_STACK_FRAMES"); + if (result) { + return atoi(result); + } + return 8; + }(); + + if (n_stack_frames == 0) { + return; + } + + for (uint64_t frame_index = 0; frame_index < stack_frames_size; ++frame_index) { + image_offset image_offset = image_offsets[frame_index]; + if (image_offset.offset && !uuid_is_null(image_offset.uuid) && + uuid_compare(image_offset.uuid, _stack_frame_uuid)) { + + _encoder.encode_varint(field_number * 8 + 2); + _encoder.begin_length_delimited(); + + const uuid_t cached_uuid = {}; + uint64_t image_offset_id = _image_offset_cache.lookup(image_offset.uuid, &cached_uuid); + if (!cached_uuid) { + uuid_t *key = _heap.alloc(); + uuid_copy(*key, image_offset.uuid); + + image_offset_id = _image_offset_cache.count(); + _image_offset_cache.insert(*key, image_offset_id); + + _encoder.encode_varint(0x1a); + _encoder.begin_length_delimited(); + + uuid_string_t uuid_string = {}; + uuid_unparse(*key, uuid_string); + + _encoder.encode_varint(0xa); + _encoder.encode_data(uuid_string, sizeof(uuid_string_t) - 1); // don't encode trailing NULL character + + Dl_info dl_info; + if (dladdr(stack_frames_buffer[frame_index], &dl_info)) { + if (dl_info.dli_fname) { + auto length = strlen(dl_info.dli_fname); + _encoder.encode_varint(0x12); + _encoder.encode_data((void *)dl_info.dli_fname, length); + } + if (dl_info.dli_fbase) { + _encoder.encode_varint(0x18); + _encoder.encode_varint((uintptr_t)dl_info.dli_fbase); + + // TODO: what is the correct ptrauth key? + mach_vm_address_t address = + (mach_vm_address_t)ptrauth_strip(dl_info.dli_fbase, ptrauth_key_process_independent_code); + mach_vm_size_t size = 0; + vm_region_info_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t object_name = MACH_PORT_NULL; + kern_return_t status = mach_vm_region(mach_task_self(), &address, &size, + VM_REGION_BASIC_INFO_64, info, &info_count, &object_name); + if (object_name) { + mach_port_deallocate(mach_task_self(), object_name); + } + if (status == KERN_SUCCESS) { + if (size) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(size); + } + } + } + } + + _encoder.end_length_delimited(); + } + + if (image_offset_id) { + _encoder.encode_varint(8); + _encoder.encode_varint(image_offset_id); + } + if (image_offset.offset) { + _encoder.encode_varint(0x10); + _encoder.encode_varint(image_offset.offset); + } + + _encoder.end_length_delimited(); + } + } +} + +int Graph::TraceRecorder::flush_encoder(Encoder &encoder) { + int fd = -1; + if (_trace_file_exists) { + fd = open(_trace_path, O_WRONLY | O_APPEND, 0666); + } else { + _trace_file_exists = true; + + const char *trace_file = getenv("AG_TRACE_FILE"); + if (!trace_file) { + trace_file = "trace"; + } + + const char *dir = getenv("TMPDIR"); + if (!dir || !*dir) { + dir = "/tmp"; + } + + const char *separator = dir[strlen(dir) - 1] == '/' ? "" : "/"; + + char *attempted_file_name = nullptr; + for (int attempt = 1; attempt <= 999; ++attempt) { + asprintf(&attempted_file_name, "%s%s%s-%04d.ag-trace", dir, separator, trace_file, attempt); + fd = open(attempted_file_name, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (fd != -1) { + break; + } + if (attempted_file_name) { + free(attempted_file_name); + attempted_file_name = nullptr; + } + if (errno != EEXIST) { + break; + } + } + + const char *old_file_name = _trace_path; + _trace_path = attempted_file_name; + if (old_file_name) { + free((void *)old_file_name); + } + + if (_trace_path) { + os_log(misc_log(), "created trace file %s", _trace_path); + fprintf(stdout, "created trace file \"%s\" (pid %d)\n", _trace_path, getpid()); + } else { + fprintf(stdout, "failed to create trace file: %s%s%s-XXXX.ag-trace\n", dir, separator, trace_file); + } + } + if (fd == -1) { + return; + } + + const char *buffer = encoder.buffer().data(); + size_t remaining = encoder.buffer().size(); + while (remaining > 0) { + ssize_t written = write(fd, buffer, remaining); + if (written < 0) { + if (errno == EINTR) { + // try again on interrupted error + continue; + } + unlink(_trace_path); + break; + } + buffer += written; + remaining -= written; + } + return close(fd); +} + +#pragma mark - Trace methods + +void Graph::TraceRecorder::begin_trace(const Graph &graph) { + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(1); + field_timestamp(_encoder); + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_trace(const Graph &graph) { + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(2); + field_timestamp(_encoder); + _encoder.end_length_delimited(); + + encode_snapshot(); +} + +void Graph::TraceRecorder::sync_trace() { + encode_snapshot(); + _encoder.flush(); +} + +void Graph::TraceRecorder::log_message_v(const char *format, va_list args) { + char *message = nullptr; + vasprintf(&message, format, args); + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x33); + field_timestamp(_encoder); + field_backtrace(_encoder, 8); + + size_t length = strlen(message); + if (length > 0) { + _encoder.encode_varint(0x4a); + _encoder.encode_data(message, length); + } + + _encoder.end_length_delimited(); + + encode_stack(); + + if (message) { + free(message); + } +} + +void Graph::TraceRecorder::begin_update(const Subgraph &subgraph, uint32_t options) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(3); + field_timestamp(_encoder); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + if (options) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(options); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_update(const Subgraph &subgraph) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(4); + field_timestamp(_encoder); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_update(const Graph::UpdateStack &update_stack, data::ptr node, + uint32_t options) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(5); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (options) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(options); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_update(const Graph::UpdateStack &update_stack, data::ptr node, + Graph::UpdateStatus update_status) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(6); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (update_status == Graph::UpdateStatus::Changed) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(Graph::UpdateStatus::Changed); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_update(data::ptr node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(7); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_update(data::ptr node, bool changed) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(8); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (changed) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(true); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_update(const Graph::Context &context) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(9); + field_timestamp(_encoder); + + if (context.unique_id()) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(context.unique_id()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_update(const Graph::Context &context) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(10); + field_timestamp(_encoder); + + if (context.unique_id()) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(context.unique_id()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_invalidation(const Graph::Context &context, AttributeID attribute) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0xb); + field_timestamp(_encoder); + + if (attribute) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(attribute); + } + if (context.unique_id()) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(context.unique_id()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_invalidation(const Graph::Context &context, AttributeID attribute) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0xc); + field_timestamp(_encoder); + + if (attribute) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(attribute); + } + if (context.unique_id()) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(context.unique_id()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_modify(data::ptr node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0xd); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_modify(data::ptr node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0xe); + field_timestamp(_encoder); + + if (node || _tracing_flags & TracingFlags::Custom) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(1); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::begin_event(data::ptr node, uint32_t event_id) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0xf); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (event_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(event_id); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::end_event(data::ptr node, uint32_t event_id) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x10); + field_timestamp(_encoder); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (event_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(event_id); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::created(const Graph::Context &context) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x20); + + if (context.unique_id()) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(context.unique_id()); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::destroy(const Graph::Context &context) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x21); + + if (context.unique_id()) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(context.unique_id()); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::needs_update(const Graph::Context &context) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x22); + + if (context.unique_id()) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(context.unique_id()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::created(const Subgraph &subgraph) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x23); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + if (subgraph.context_id()) { + _encoder.encode_varint(0x28); + _encoder.encode_varint(subgraph.context_id()); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::invalidate(const Subgraph &subgraph) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x24); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); + + _encoder.begin_length_delimited(); + _encoder.encode_varint(0x12); + subgraph.encode(_encoder); + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::destroy(const Subgraph &subgraph) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x35); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::add_child(const Subgraph &subgraph, const Subgraph &child) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x25); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + auto child_zone_id = child.info().zone_id(); + if (child_zone_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(child_zone_id); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::remove_child(const Subgraph &subgraph, const Subgraph &child) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x26); + + auto zone_id = subgraph.info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(zone_id); + } + auto child_zone_id = child.info().zone_id(); + if (child_zone_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(child_zone_id); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::added(data::ptr node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x27); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + auto zone_id = AttributeID(node).subgraph()->info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(zone_id); + } + auto type_id = node->type_id(); + if (type_id) { + _encoder.encode_varint(0x28); + _encoder.encode_varint(type_id); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::add_edge(data::ptr node, AttributeID input, uint8_t input_edge_flags) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2f); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (input) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(input); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::remove_edge(data::ptr node, uint32_t input_index) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x30); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + auto input_edge = node->inputs()[input_index]; + if (input_edge.value) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(input_edge.value); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_edge_pending(data::ptr node, uint32_t input_index, bool pending) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x30); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + auto input_edge = node->inputs()[input_index]; + if (input_edge.value) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(input_edge.value); + } + if (pending) { + _encoder.encode_varint(0x28); + _encoder.encode_varint(1); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_dirty(data::ptr node, bool dirty) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x28); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (dirty) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(1); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_pending(data::ptr node, bool pending) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x29); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + if (pending) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(1); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_value(data::ptr node, const void *value) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2a); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::mark_value(data::ptr node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + if ((_tracing_flags & TracingFlags::Full) == 0) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2b); + + if (node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(node.offset()); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::added(data::ptr indirect_node) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2c); + + if (indirect_node) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(AttributeID(indirect_node)); + } + auto zone_id = AttributeID(indirect_node).subgraph()->info().zone_id(); + if (zone_id) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(zone_id); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_source(data::ptr indirect_node, AttributeID source) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2d); + + _encoder.encode_varint(0x18); + _encoder.encode_varint(AttributeID(indirect_node)); + auto source_attribute = indirect_node->source().attribute(); + if (source_attribute) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(source_attribute); + } + auto subgraph_id = indirect_node->source().subgraph_id(); + if (subgraph_id) { + _encoder.encode_varint(0x28); + _encoder.encode_varint(subgraph_id); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_dependency(data::ptr indirect_node, AttributeID dependency) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x2e); + + _encoder.encode_varint(0x18); + _encoder.encode_varint(AttributeID(indirect_node)); + auto dependency_attribute = indirect_node->to_mutable().dependency(); + if (dependency_attribute) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(dependency_attribute); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::set_deadline(uint64_t deadline) { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x37); + + field_timestamp(_encoder); + + if (deadline & 0xffffffff) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(deadline & 0xffffffff); + } + if (deadline >> 32) { + _encoder.encode_varint(0x20); + _encoder.encode_varint(deadline >> 32); + } + + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::passed_deadline() { + if (_tracing_flags & TracingFlags::Custom) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x38); + + field_timestamp(_encoder); + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); + + encode_stack(); +} + +void Graph::TraceRecorder::mark_profile(const Graph &graph, uint32_t options) { + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x32); + + field_timestamp(_encoder); + if (options) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(options); + } + field_backtrace(_encoder, 8); + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::custom_event(const Graph::Context &context, const char *event_name, const void *value, + const swift::metadata &type) { + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x34); + + field_timestamp(_encoder); + field_backtrace(_encoder, 8); + + auto length = strlen(event_name); + if (length > 0) { + _encoder.encode_varint(0x4a); + _encoder.encode_data((void *)event_name, length); + } + + _encoder.end_length_delimited(); +} + +void Graph::TraceRecorder::named_event(const Graph::Context &context, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6) { + if (!named_event_enabled(event_id)) { + return; + } + + _encoder.encode_varint(10); + _encoder.begin_length_delimited(); + _encoder.encode_varint(8); + _encoder.encode_varint(0x36); + + if (event_id) { + _encoder.encode_varint(0x50); + _encoder.encode_varint(event_id); + } + + field_timestamp(_encoder); + + if (arg6 < 0) { + field_backtrace(_encoder, 8); + arg6 &= 0x7fffffff; + } + if (arg6) { + _encoder.encode_varint(0x18); + _encoder.encode_varint(arg6); + } + + if (num_event_args >= 4) { + num_event_args = 4; + } + for (auto i = 0; i < num_event_args; ++i) { + uint64_t event_arg = event_args[i]; + if (event_arg) { + _encoder.encode_varint(0x20 + 8 * i); + _encoder.encode_varint(event_arg); + } + } + + if (data != nullptr) { + void *ptr = (void *)CFDataGetBytePtr(data); + uint64_t length = CFDataGetLength(data); + if (length > 0) { + _encoder.encode_varint(0x4a); + _encoder.encode_data(ptr, length); + } + } + + _encoder.end_length_delimited(); +} + +bool Graph::TraceRecorder::named_event_enabled(uint32_t event_id) { + uint32_t index = 0; + if (!_named_event_infos.empty()) { + auto pos = std::lower_bound( + _named_event_infos.begin(), _named_event_infos.end(), event_id, + [](const NamedEventInfo &info, uint32_t event_id) -> bool { return info.event_id < event_id; }); + if (pos != _named_event_infos.end() && pos->event_id == event_id) { + return pos->enabled; + } + index = (uint32_t)(pos - _named_event_infos.begin()); // TODO: specify difference_type on AG::vector::iterator + } + + const char *event_name = AGGraphGetTraceEventName(event_id); + if (event_name == nullptr) { + precondition_failure("invalid named trace event: %u", event_id); + } + + const char *event_subsystem = AGGraphGetTraceEventSubsystem(event_id); + + bool enabled = false; + if (event_subsystem == nullptr || (_tracing_flags & TracingFlags::All)) { + enabled = true; + } else { + enabled = (_tracing_flags & TracingFlags::All) != 0; + for (auto stored_subsystem : _named_event_subsystems) { + if (!strcasecmp(stored_subsystem, event_subsystem)) { + enabled = true; + break; + } + } + } + _named_event_infos.insert(_named_event_infos.begin() + index, {event_id, enabled}); + + if (!enabled) { + return false; + } + + _encoder.encode_varint(0x32); + _encoder.begin_length_delimited(); + if (event_id) { + _encoder.encode_varint(8); + _encoder.encode_varint(event_id); + } + + auto event_name_length = strlen(event_name); + if (event_name_length > 0) { + _encoder.encode_varint(0x12); + _encoder.encode_data((void *)event_name, event_name_length); + } + auto event_subsystem_length = strlen(event_subsystem); + if (event_subsystem_length > 0) { + _encoder.encode_varint(0x1a); + _encoder.encode_data((void *)event_subsystem, event_subsystem_length); + } + + _encoder.end_length_delimited(); + + return true; +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/TraceRecorder.h b/Sources/ComputeCxx/Graph/TraceRecorder.h new file mode 100644 index 0000000..587cd0f --- /dev/null +++ b/Sources/ComputeCxx/Graph/TraceRecorder.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include + +#include "Encoder/Encoder.h" +#include "Trace/Trace.h" +#include "Utilities/HashTable.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Graph::TraceRecorder : public Encoder::Delegate, public Trace { + private: + uint64_t _unique_id; + + Encoder::Delegate *_Nonnull _delegate; + Graph *_graph; + Encoder _encoder; + uint8_t _tracing_flags; + + util::InlineHeap<256> _heap; + + vector _named_event_subsystems; + + util::Table _image_offset_cache; + uuid_t _stack_frame_uuid; + + const char *_Nullable _trace_path = nullptr; + bool _trace_file_exists = false; + + uint32_t _num_encoded_types = 1; // skip AGAttributeNullType + uint32_t _num_encoded_keys = 0; + + struct NamedEventInfo { + uint32_t event_id; + bool enabled; + }; + vector _named_event_infos; + + public: + TraceRecorder(Graph *graph, uint8_t tracing_flags, std::span subsystems); + ~TraceRecorder(); + + uint64_t unique_id() { return _unique_id; }; + + const char *_Nullable trace_path() const { return _trace_path; }; + + void encode_types(); + void encode_keys(); + void encode_stack(); + void encode_snapshot(); + + void field_timestamp(Encoder &encoder); + void field_backtrace(Encoder &encoder, uint64_t field); + + virtual int flush_encoder(Encoder &encoder) override; + + // MARK: Trace methods + + void begin_trace(const Graph &graph) override; + void end_trace(const Graph &graph) override; + void sync_trace() override; + + void log_message_v(const char *format, va_list args) override; + + void begin_update(const Subgraph &subgraph, uint32_t options) override; + void end_update(const Subgraph &subgraph) override; + void begin_update(const Graph::UpdateStack &update_stack, data::ptr node, uint32_t options) override; + void end_update(const Graph::UpdateStack &update_stack, data::ptr node, + Graph::UpdateStatus update_status) override; + void begin_update(data::ptr node) override; + void end_update(data::ptr node, bool changed) override; + void begin_update(const Graph::Context &context) override; + void end_update(const Graph::Context &context) override; + + void begin_invalidation(const Graph::Context &context, AttributeID attribute) override; + void end_invalidation(const Graph::Context &context, AttributeID attribute) override; + + void begin_modify(data::ptr node) override; + void end_modify(data::ptr node) override; + + void begin_event(data::ptr node, uint32_t event_id) override; + void end_event(data::ptr node, uint32_t event_id) override; + + void created(const Graph::Context &context) override; + void destroy(const Graph::Context &context) override; + void needs_update(const Graph::Context &context) override; + + void created(const Subgraph &subgraph) override; + void invalidate(const Subgraph &subgraph) override; + void destroy(const Subgraph &subgraph) override; + + void add_child(const Subgraph &subgraph, const Subgraph &child) override; + void remove_child(const Subgraph &subgraph, const Subgraph &child) override; + + void added(data::ptr node) override; + + void add_edge(data::ptr node, AttributeID input, uint8_t input_edge_flags) override; + void remove_edge(data::ptr node, uint32_t input_index) override; + void set_edge_pending(data::ptr node, uint32_t input_index, bool pending) override; + + void set_dirty(data::ptr node, bool dirty) override; + void set_pending(data::ptr node, bool pending) override; + + void set_value(data::ptr node, const void *value) override; + void mark_value(data::ptr node) override; + + void added(data::ptr indirect_node) override; + + void set_source(data::ptr indirect_node, AttributeID source) override; + void set_dependency(data::ptr indirect_node, AttributeID dependency) override; + + void set_deadline(uint64_t deadline) override; + void passed_deadline() override; + + void mark_profile(const Graph &graph, uint32_t options) override; + + void custom_event(const Graph::Context &context, const char *event_name, const void *value, + const swift::metadata &type) override; + void named_event(const Graph::Context &context, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6) override; + bool named_event_enabled(uint32_t event_id) override; + + // compare_failed not overridden +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Tree/AGTreeElement.cpp b/Sources/ComputeCxx/Graph/Tree/AGTreeElement.cpp new file mode 100644 index 0000000..0699f5d --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/AGTreeElement.cpp @@ -0,0 +1,88 @@ +#include "AGTreeElement.h" + +#include "Subgraph/Subgraph.h" +#include "TreeElement.h" + +AGTypeID AGTreeElementGetType(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + auto type = tree_element_id.to_ptr()->type; + return AGTypeID(type); +} + +// TODO: rename to value +AGAttribute AGTreeElementGetValue(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + auto node = tree_element_id.to_ptr()->node; + return AGAttribute(node); +} + +uint32_t AGTreeElementGetFlags(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + return tree_element_id.to_ptr()->flags; +} + +AGTreeElement AGTreeElementGetParent(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + return AG::TreeElementID(tree_element_id.to_ptr()->parent).to_storage(); +} + +#pragma mark - Iterating values + +AGTreeElementValueIterator AGTreeElementMakeValueIterator(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + auto tree_value = AG::TreeValueID(tree_element_id.to_ptr()->first_value); + return AGTreeElementValueIterator(tree_element, tree_value.to_storage()); +} + +AGTreeValue AGTreeElementGetNextValue(AGTreeElementValueIterator iter) { + AGTreeValue tree_value = iter.next_value; + if (tree_value) { + auto tree_value_id = AG::TreeValueID::from_storage(tree_value); + iter.next_value = AG::TreeValueID(tree_value_id.to_tree_value().next).to_storage(); + } + return tree_value; +} + +#pragma mark - Iterating values + +AGTreeElementNodeIterator AGTreeElementMakeNodeIterator(AGTreeElement tree_element) { return {tree_element, 0}; } + +AGAttribute AGTreeElementGetNextNode(AGTreeElementNodeIterator *iter) { + auto tree_element_id = AG::TreeElementID::from_storage(iter->tree_element); + AG::AttributeID node = tree_element_id.subgraph()->tree_node_at_index(tree_element_id.to_ptr(), iter->index); + if (!node.has_value()) { + return AGAttributeNil; + } + iter->index += 1; + return AGAttribute(node); +} + +#pragma mark - Iterating children + +AGTreeElementChildIterator AGTreeElementMakeChildIterator(AGTreeElement tree_element) { + auto tree_element_id = AG::TreeElementID::from_storage(tree_element); + auto child = AG::TreeElementID(tree_element_id.to_ptr()->first_child); + return AGTreeElementChildIterator(tree_element, child.to_storage(), 0); +} + +AGTreeElement AGTreeElementGetNextChild(AGTreeElementChildIterator *iter) { + AGTreeElement next_child = iter->next_child; + if (next_child) { + iter->next_child = + AG::TreeElementID(AG::TreeElementID::from_storage(next_child).to_ptr()->next_sibling).to_storage(); + return next_child; + } + + if (!iter->iterated_subgraph) { + iter->iterated_subgraph = true; + auto tree_element_id = AG::TreeElementID::from_storage(iter->tree_element); + auto subgraph = tree_element_id.subgraph(); + auto next_child = subgraph->tree_subgraph_child(tree_element_id.to_ptr()); + if (next_child) { + iter->next_child = AG::TreeElementID(AG::TreeElementID(next_child).to_ptr()->next_sibling).to_storage(); + return AG::TreeElementID(next_child).to_storage(); + } + } + + return 0; +} diff --git a/Sources/ComputeCxx/Graph/Tree/AGTreeElement.h b/Sources/ComputeCxx/Graph/Tree/AGTreeElement.h new file mode 100644 index 0000000..5e0e648 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/AGTreeElement.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "AGTreeValue.h" +#include "Attribute/AGAttribute.h" +#include "Swift/AGType.h" + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +typedef uint32_t AGTreeElement AG_SWIFT_STRUCT AG_SWIFT_NAME(TreeElement); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTypeID AGTreeElementGetType(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGTreeElementGetValue(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint32_t AGTreeElementGetFlags(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElement AGTreeElementGetParent(AGTreeElement tree_element); + +// MARK: Iterating values + +typedef struct AGTreeElementValueIterator { + AGTreeElement tree_element; + AGTreeValue next_value; +} AG_SWIFT_NAME(Values) AGTreeElementValueIterator; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElementValueIterator AGTreeElementMakeValueIterator(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeValue AGTreeElementGetNextValue(AGTreeElementValueIterator iter); + +// MARK: Iterating nodes + +typedef struct AGTreeElementNodeIterator { + AGTreeElement tree_element; + uint32_t index; +} AG_SWIFT_NAME(Nodes) AGTreeElementNodeIterator; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElementNodeIterator AGTreeElementMakeNodeIterator(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGAttribute AGTreeElementGetNextNode(AGTreeElementNodeIterator *iter); + +// MARK: Iterating children + +typedef struct AGTreeElementChildIterator { + AGTreeElement tree_element; + AGTreeElement next_child; + bool iterated_subgraph; +} AG_SWIFT_NAME(Children) AGTreeElementChildIterator; + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElementChildIterator AGTreeElementMakeChildIterator(AGTreeElement tree_element); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElement AGTreeElementGetNextChild(AGTreeElementChildIterator *iter); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Tree/AGTreeValue.cpp b/Sources/ComputeCxx/Graph/Tree/AGTreeValue.cpp new file mode 100644 index 0000000..dbbe314 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/AGTreeValue.cpp @@ -0,0 +1,26 @@ +#include "AGTreeValue.h" + +#include "Graph/Graph.h" +#include "Subgraph/Subgraph.h" +#include "TreeElement.h" + +AGTypeID AGTreeValueGetType(AGTreeValue tree_value) { + auto tree_value_id = AG::TreeValueID::from_storage(tree_value); + return AGTypeID(tree_value_id.to_tree_value().type); +} + +AGAttribute AGTreeValueGetValue(AGTreeValue tree_value) { + auto tree_value_id = AG::TreeValueID::from_storage(tree_value); + return AGAttribute(tree_value_id.to_tree_value().value); +} + +const char *AGTreeValueGetKey(AGTreeValue tree_value) { + auto tree_value_id = AG::TreeValueID::from_storage(tree_value); + auto key_id = tree_value_id.to_tree_value().key_id; + return tree_value_id.subgraph()->graph()->key_name(key_id); +} + +uint32_t AGTreeValueGetFlags(AGTreeValue tree_value) { + auto tree_value_id = AG::TreeValueID::from_storage(tree_value); + return tree_value_id.to_tree_value().flags; +} diff --git a/Sources/ComputeCxx/Graph/Tree/AGTreeValue.h b/Sources/ComputeCxx/Graph/Tree/AGTreeValue.h new file mode 100644 index 0000000..38aea3f --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/AGTreeValue.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "Attribute/AGAttribute.h" +#include "Swift/AGType.h" + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +typedef uint32_t AGTreeValue AG_SWIFT_STRUCT AG_SWIFT_NAME(TreeValue); + +CF_EXPORT +AGTypeID AGTreeValueGetType(AGTreeValue tree_value); + +CF_EXPORT +AGAttribute AGTreeValueGetValue(AGTreeValue tree_value); + +CF_EXPORT +const char *AGTreeValueGetKey(AGTreeValue tree_value); + +CF_EXPORT +uint32_t AGTreeValueGetFlags(AGTreeValue tree_value); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/Tree/TreeElement.cpp b/Sources/ComputeCxx/Graph/Tree/TreeElement.cpp new file mode 100644 index 0000000..26e115b --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/TreeElement.cpp @@ -0,0 +1,15 @@ +#include "TreeElement.h" + +namespace AG { + +void Graph::TreeDataElement::sort_nodes() { + if (!_sorted) { + std::sort( + _nodes.begin(), _nodes.end(), [](const TreeElementNodePair &a, const TreeElementNodePair &b) -> bool { + return a.first != b.first ? a.first.offset() < b.first.offset() : a.second.offset() < b.second.offset(); + }); + _sorted = true; + } +}; + +}; // namespace AG diff --git a/Sources/ComputeCxx/Graph/Tree/TreeElement.h b/Sources/ComputeCxx/Graph/Tree/TreeElement.h new file mode 100644 index 0000000..a018c86 --- /dev/null +++ b/Sources/ComputeCxx/Graph/Tree/TreeElement.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +#include "Data/Pointer.h" +#include "Graph/Graph.h" +#include "Swift/Metadata.h" +#include "Vector/Vector.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +struct Graph::TreeElement { + const swift::metadata *type; + + AttributeID node; // TODO: check named value from AGSubgraphBeginTreeElement + uint32_t flags; + + data::ptr parent; + data::ptr first_child; + data::ptr next_sibling; + + data::ptr first_value; +}; +static_assert(sizeof(Graph::TreeElement) == 0x20); + +class TreeElementID { + private: + uint32_t _value; + + explicit constexpr TreeElementID(uint32_t value) : _value(value) {}; + + public: + explicit TreeElementID(data::ptr tree_element) : _value(tree_element.offset()) {}; + + constexpr uint32_t to_storage() const { return _value; } + static constexpr TreeElementID from_storage(uint32_t value) { return TreeElementID(value); } + + data::ptr to_ptr() const { return data::ptr(_value); }; + + Subgraph *_Nullable subgraph() const { return reinterpret_cast(page_ptr()->zone); } + data::ptr page_ptr() const { return data::ptr(_value).page_ptr(); }; + + bool has_value() const { return _value != 0; } +}; + +struct Graph::TreeValue { + const swift::metadata *_Nonnull type; + AttributeID value; + uint32_t key_id; + uint32_t flags; + data::ptr next; +}; +static_assert(sizeof(Graph::TreeValue) == 0x18); + +class TreeValueID { + private: + uint32_t _value; + + explicit constexpr TreeValueID(uint32_t value) : _value(value) {}; + + public: + explicit TreeValueID(data::ptr tree_value) : _value(tree_value.offset()) {}; + + constexpr uint32_t to_storage() const { return _value; } + static constexpr TreeValueID from_storage(uint32_t value) { return TreeValueID(value); } + + const Graph::TreeValue &to_tree_value() const { return *data::ptr(_value); }; + + Subgraph *_Nullable subgraph() const { return reinterpret_cast(page_ptr()->zone); } + data::ptr page_ptr() const { return data::ptr(_value).page_ptr(); }; +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Graph/UpdateStack.cpp b/Sources/ComputeCxx/Graph/UpdateStack.cpp new file mode 100644 index 0000000..9211775 --- /dev/null +++ b/Sources/ComputeCxx/Graph/UpdateStack.cpp @@ -0,0 +1,348 @@ +#include "UpdateStack.h" + +#include "AGGraph.h" +#include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" +#include "Attribute/Node/Node.h" +#include "Attribute/OffsetAttributeID.h" +#include "Subgraph/Subgraph.h" +#include "Trace/Trace.h" + +namespace AG { + +Graph::UpdateStack::UpdateStack(Graph *graph, uint8_t options) { + _graph = graph; + _thread = pthread_self(); + _previous = current_update(); + _previous_thread = graph->_current_update_thread; + + _options = options; + if (_previous != nullptr) { + _options = _options | (_previous.get()->_options & 4); + } + + graph->_current_update_thread = _thread; + + if (graph->_deferring_subgraph_invalidation == false) { + graph->_deferring_subgraph_invalidation = true; + _options &= Option::InvalidateSubgraphs; + } + + Graph::set_current_update(util::tagged_ptr(this, options & Option::SetTag ? 1 : 0)); +} + +Graph::UpdateStack::~UpdateStack() { + for (auto frame : _frames) { + frame.attribute->set_state(frame.attribute->state().with_updating(false)); + } + + if (_thread != _graph->_current_update_thread) { + non_fatal_precondition_failure("invalid graph update (access from multiple threads?)"); + } + + _graph->_current_update_thread = _previous_thread; + Graph::set_current_update(_previous); + + if (_options & Option::InvalidateSubgraphs) { + _graph->_deferring_subgraph_invalidation = false; + } +} + +Graph::UpdateStack::Frame *Graph::UpdateStack::global_top() { + for (util::tagged_ptr update_stack = this; update_stack != nullptr; + update_stack = update_stack.get()->previous()) { + if (!update_stack.get()->frames().empty()) { + return &update_stack.get()->frames().back(); + } + } + return nullptr; +} + +void Graph::UpdateStack::cancel() { + for (util::tagged_ptr update_stack = current_update(); update_stack != nullptr; + update_stack = update_stack.get()->previous()) { + auto &frames = update_stack.get()->frames(); + for (auto frame = frames.rbegin(), end = frames.rend(); frame != end; ++frame) { + if (frame->cancelled) { + return; + } + frame->cancelled = true; + } + if (update_stack.get()->_options & 2) { + break; + } + } +} + +bool Graph::UpdateStack::cancelled() { + for (util::tagged_ptr update_stack = this; update_stack != nullptr; + update_stack = update_stack.get()->previous()) { + if (!update_stack.get()->frames().empty()) { + auto last_frame = update_stack.get()->frames().back(); + return last_frame.cancelled; + } + } + return false; +} + +bool Graph::UpdateStack::push(data::ptr attribute, Node &node, bool flag1, bool treat_no_value_as_pending) { + if (!node.state().is_updating() && _frames.size() + 1 <= _frames.capacity()) { + node.set_state(node.state().with_updating(true)); + + Graph::UpdateStack::Frame frame = {attribute, 0}; + if (node.state().is_pending() || (!node.state().is_value_initialized() && treat_no_value_as_pending)) { + frame.needs_update = true; + } + _frames.push_back(frame); + return true; + } + + return push_slow(attribute, node, flag1, treat_no_value_as_pending); +} + +bool Graph::UpdateStack::push_slow(data::ptr attribute, Node &node, bool ignore_cycles, + bool treat_no_value_as_pending) { + Node::State old_state = node.state(); + + if (old_state.is_updating()) { + if (ignore_cycles) { + return false; + } + + Graph::UpdateStack::Frame *top = global_top(); + if (top != nullptr && !top->cyclic) { + // First time we are detecting a cycle for this attribute + this->_graph->print_cycle(attribute); + } + + if (node.state().is_value_initialized()) { + return false; + } + + const AttributeType &attribute_type = _graph->attribute_type(node.type_id()); + auto callback = attribute_type.vt_get_update_stack_callback(); + if (callback != nullptr) { + + Graph::UpdateStack::Frame frame = {attribute, 0}; + if (node.state().is_pending() || (!node.state().is_value_initialized() && treat_no_value_as_pending)) { + frame.needs_update = true; + } + + _frames.push_back(frame); + + void *self = node.get_self(attribute_type); + callback(&attribute_type, self); + + _frames.pop_back(); + + if (node.state().is_value_initialized()) { + return false; + } + } + + if (old_state.is_updating_cyclic()) { + precondition_failure("cyclic graph: %u", attribute); + } + } + + node.set_state(node.state().with_updating(true)); + + Graph::UpdateStack::Frame frame = {attribute, 0}; + if (node.state().is_pending() || (!node.state().is_value_initialized() && treat_no_value_as_pending)) { + frame.needs_update = true; + } + if (old_state.is_updating()) { + frame.cyclic = true; + } + _frames.push_back(frame); + + return true; +} + +Graph::UpdateStatus Graph::UpdateStack::update() { + + while (true) { + auto frame = _frames.back(); + Node *node = frame.attribute.get(); + + if (_options >> 2) { + if (!frame.cancelled && _graph->passed_deadline()) { + cancel(); + } + } + + if (frame.cancelled) { + if (_options >> 1) { + return UpdateStatus::Option2; + } + + bool changed = false; + if (!node->state().is_value_initialized()) { + const AttributeType &type = _graph->attribute_type(node->type_id()); + void *self = node->get_self(type); + if (auto callback = type.vt_get_update_stack_callback()) { + callback(&type, self); + changed = true; + } + if (node->state().is_value_initialized()) { + node->set_state(node->state().with_updating(false)); + _frames.pop_back(); + if (_frames.empty()) { + return changed ? UpdateStatus::Changed : UpdateStatus::NoChange; + } + } + } + } + + if (!frame.needs_update && frame.num_pushed_inputs > 0 && + node->inputs()[frame.num_pushed_inputs - 1].is_changed()) { + frame.needs_update = true; + } + + // Push inputs + + for (auto input_index = frame.num_pushed_inputs, num_inputs = node->inputs().size(); input_index != num_inputs; + ++input_index) { + InputEdge &input = node->inputs()[input_index]; + + AttributeID input_attribute = input.value; + while (input_attribute.is_indirect()) { + AttributeID input_attribute = input_attribute.to_indirect_node().source().attribute(); + + if (input_attribute.to_indirect_node().is_mutable()) { + // TDOO: is dependency always direct?? + if (AttributeID dependency = input_attribute.to_indirect_node().to_mutable().dependency()) { + if (dependency.to_node().state().is_dirty() || + !dependency.to_node().state().is_value_initialized()) { + + frame.num_pushed_inputs = input_index; + + push(dependency.to_ptr(), dependency.to_node(), false, false); + // go to top regardless + return update(); + } + } + } + } + + if (input_attribute.is_direct()) { + Node &input_node = input_attribute.to_node(); + + if (input.is_changed()) { + frame.needs_update = true; + } + + if (input_node.state().is_dirty() || !input_node.state().is_value_initialized()) { + + if (!input.is_changed() && + input_attribute.subgraph()->validation_state() == Subgraph::ValidationState::Valid) { + + frame.num_pushed_inputs = input_index + 1; + + if (push(input_attribute.to_ptr(), input_node, true, true)) { + // go to top + return update(); + } + } + + frame.needs_update = true; + } + } + } + + // Update value + + bool changed = false; + if (frame.needs_update) { + if (_graph->main_handler() != nullptr && node->state().is_main_thread()) { + return Graph::UpdateStatus::NeedsCallMainHandler; + } + + _graph->foreach_trace([&frame](Trace &trace) { trace.begin_update(frame.attribute); }); + uint64_t old_change_count = _graph->_change_count; + + const AttributeType &type = _graph->attribute_type(node->type_id()); + void *self = node->get_self(type); + + type.perform_update(self, AttributeID(frame.attribute)); // could be ptr? + + if (!node->state().is_value_initialized()) { + if (type.value_metadata().vw_size() > 0) { + precondition_failure("attribute failed to set an initial value: %u, %s", frame.attribute, + type.self_metadata().name(false)); + } + + struct { + } value = {}; + AGGraphSetOutputValue(&value, AGTypeID(&type.value_metadata())); + } + + changed = _graph->_change_count != old_change_count; + _graph->foreach_trace([&frame, &changed](Trace &trace) { trace.end_update(frame.attribute, changed); }); + } + + // Reset flags + + bool reset_node_flags = !frame.cancelled; + for (uint32_t input_index = node->inputs().size() - 1; input_index >= 0; --input_index) { + InputEdge &input = node->inputs()[input_index]; + AttributeID input_attribute = input.value; + + bool reset_input_flags = true; + if (frame.flag3 || frame.cancelled) { + input_attribute = input_attribute.resolve(TraversalOptions::None).attribute(); + if (!input_attribute.is_direct() || input_attribute.to_node().state().is_dirty()) { + reset_input_flags = false; + reset_node_flags = false; + } + } + + if (reset_input_flags) { + if (frame.needs_update && !frame.cancelled) { + if (input.is_changed()) { + _graph->foreach_trace([&frame, &input](Trace &trace) { + trace.set_edge_pending(frame.attribute, input.value, false); + }); + input.set_changed(false); + } + } + } + + if (frame.needs_update && !frame.cancelled) { + bool was_unknown4 = input.is_unknown4(); + input.set_unknown4(false); + if (!was_unknown4 && !input.is_always_enabled()) { + _graph->remove_input(frame.attribute, input_index); + } + } + } + + node->set_state(node->state().with_updating(false)); + + if (reset_node_flags) { + // only skips if frame.cancelled + if (node->flags().self_modified()) { + node->flags().set_self_modified(false); + } + if (node->state().is_dirty()) { + _graph->foreach_trace([&frame](Trace &trace) { trace.set_dirty(frame.attribute, false); }); + node->set_state(node->state().with_dirty(false)); + } + if (!node->state().is_main_thread_only()) { + node->set_state(node->state().with_main_thread(false)); + } + } + + if (node->state().is_pending()) { + _graph->foreach_trace([&frame](Trace &trace) { trace.set_pending(frame.attribute, false); }); + node->set_state(node->state().with_pending(false)); + } + + _frames.pop_back(); + if (_frames.empty()) { + return changed ? UpdateStatus::Changed : UpdateStatus::NoChange; + } + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Graph/UpdateStack.h b/Sources/ComputeCxx/Graph/UpdateStack.h new file mode 100644 index 0000000..e62889e --- /dev/null +++ b/Sources/ComputeCxx/Graph/UpdateStack.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include "Graph.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Graph::UpdateStack { + public: + enum Option : uint8_t { + Unknown0x02 = 1 << 1, // TODO: set from AGGraphPrefetchValue + Unknown0x04 = 1 << 2, // TODO: set from AGGraphPrefetchValue + SetTag = 1 << 3, + InvalidateSubgraphs = 1 << 4, + }; + + private: + struct Frame { + data::ptr attribute; + unsigned int needs_update : 1; + unsigned int cyclic : 1; + unsigned int flag3 : 1; + unsigned int cancelled : 1; + unsigned int num_pushed_inputs : 28; + }; + + Graph *_graph; + pthread_t _thread; + util::tagged_ptr _previous; + pthread_t _previous_thread; + vector _frames; + uint8_t _options; + + bool push_slow(data::ptr attribute, Node &node, bool ignore_cycles, bool treat_no_value_as_pending); + + public: + UpdateStack(Graph *graph, uint8_t options); + ~UpdateStack(); + + Graph *graph() const { return _graph; }; + const util::tagged_ptr previous() const { return _previous; }; + + vector &frames() { return _frames; }; + + Frame *global_top(); + + static void cancel(); + bool cancelled(); + + bool push(data::ptr attribute, Node &node, bool ignore_cycles, bool treat_no_value_as_pending); + + Graph::UpdateStatus update(); +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Log/Log.cpp b/Sources/ComputeCxx/Log/Log.cpp new file mode 100644 index 0000000..437574c --- /dev/null +++ b/Sources/ComputeCxx/Log/Log.cpp @@ -0,0 +1,12 @@ +#include + +#include "Graph/Graph.h" + +namespace AG { + +os_log_t misc_log() { + static os_log_t log = os_log_create("dev.incrematic.compute", "misc"); + return log; +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Log/Log.h b/Sources/ComputeCxx/Log/Log.h new file mode 100644 index 0000000..7d7d174 --- /dev/null +++ b/Sources/ComputeCxx/Log/Log.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +os_log_t misc_log(); + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Private/CFRuntime.h b/Sources/ComputeCxx/Private/CFRuntime.h new file mode 100644 index 0000000..538f683 --- /dev/null +++ b/Sources/ComputeCxx/Private/CFRuntime.h @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2015 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* CFRuntime.h + Copyright (c) 1999-2014, Apple Inc. All rights reserved. + */ + +#if !defined(__COREFOUNDATION_CFRUNTIME__) +#define __COREFOUNDATION_CFRUNTIME__ 1 + +#include +#include +#include + +CF_EXTERN_C_BEGIN + +#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) + +// GC: until we link against ObjC must use indirect functions. Overridden in CFSetupFoundationBridging +CF_EXPORT bool kCFUseCollectableAllocator; +CF_EXPORT bool (*__CFObjCIsCollectable)(void *); + +CF_INLINE Boolean _CFAllocatorIsSystemDefault(CFAllocatorRef allocator) { + if (allocator == kCFAllocatorSystemDefault) return true; + if (NULL == allocator || kCFAllocatorDefault == allocator) { + return (kCFAllocatorSystemDefault == CFAllocatorGetDefault()); + } + return false; +} + +// is GC on? +#define CF_USING_COLLECTABLE_MEMORY (kCFUseCollectableAllocator) +// is GC on and is this the GC allocator? +#define CF_IS_COLLECTABLE_ALLOCATOR(allocator) (kCFUseCollectableAllocator && (NULL == (allocator) || kCFAllocatorSystemDefault == (allocator) || 0)) +// is this allocated by the collector? +#define CF_IS_COLLECTABLE(obj) (__CFObjCIsCollectable ? __CFObjCIsCollectable((void*)obj) : false) + +#else + +#define kCFUseCollectableAllocator 0 +#define __CFObjCIsCollectable 0 + +CF_INLINE Boolean _CFAllocatorIsSystemDefault(CFAllocatorRef allocator) { + if (allocator == kCFAllocatorSystemDefault) return true; + if (NULL == allocator || kCFAllocatorDefault == allocator) { + return (kCFAllocatorSystemDefault == CFAllocatorGetDefault()); + } + return false; +} + +#define CF_USING_COLLECTABLE_MEMORY 0 +#define CF_IS_COLLECTABLE_ALLOCATOR(allocator) 0 +#define CF_IS_COLLECTABLE(obj) 0 +#endif + +enum { + _kCFRuntimeNotATypeID = 0 +}; + +enum { // Version field constants + _kCFRuntimeScannedObject = (1UL << 0), + _kCFRuntimeResourcefulObject = (1UL << 2), // tells CFRuntime to make use of the reclaim field + _kCFRuntimeCustomRefCount = (1UL << 3), // tells CFRuntime to make use of the refcount field + _kCFRuntimeRequiresAlignment = (1UL << 4), // tells CFRuntime to make use of the requiredAlignment field +}; + +typedef struct __CFRuntimeClass { + CFIndex version; + const char *className; // must be a pure ASCII string, nul-terminated + void (*init)(CFTypeRef cf); + CFTypeRef (*copy)(CFAllocatorRef allocator, CFTypeRef cf); + void (*finalize)(CFTypeRef cf); + Boolean (*equal)(CFTypeRef cf1, CFTypeRef cf2); + CFHashCode (*hash)(CFTypeRef cf); + CFStringRef (*copyFormattingDesc)(CFTypeRef cf, CFDictionaryRef formatOptions); // return str with retain + CFStringRef (*copyDebugDesc)(CFTypeRef cf); // return str with retain + + #define CF_RECLAIM_AVAILABLE 1 + void (*reclaim)(CFTypeRef cf); // Or in _kCFRuntimeResourcefulObject in the .version to indicate this field should be used + + #define CF_REFCOUNT_AVAILABLE 1 + uint32_t (*refcount)(intptr_t op, CFTypeRef cf); // Or in _kCFRuntimeCustomRefCount in the .version to indicate this field should be used + // this field must be non-NULL when _kCFRuntimeCustomRefCount is in the .version field + // - if the callback is passed 1 in 'op' it should increment the 'cf's reference count and return 0 + // - if the callback is passed 0 in 'op' it should return the 'cf's reference count, up to 32 bits + // - if the callback is passed -1 in 'op' it should decrement the 'cf's reference count; if it is now zero, 'cf' should be cleaned up and deallocated (the finalize callback above will NOT be called unless the process is running under GC, and CF does not deallocate the memory for you; if running under GC, finalize should do the object tear-down and free the object memory); then return 0 + // remember to use saturation arithmetic logic and stop incrementing and decrementing when the ref count hits UINT32_MAX, or you will have a security bug + // remember that reference count incrementing/decrementing must be done thread-safely/atomically + // objects should be created/initialized with a custom ref-count of 1 by the class creation functions + // do not attempt to use any bits within the CFRuntimeBase for your reference count; store that in some additional field in your CF object + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmissing-field-initializers" + #define CF_REQUIRED_ALIGNMENT_AVAILABLE 1 + uintptr_t requiredAlignment; // Or in _kCFRuntimeRequiresAlignment in the .version field to indicate this field should be used; the allocator to _CFRuntimeCreateInstance() will be ignored in this case; if this is less than the minimum alignment the system supports, you'll get higher alignment; if this is not an alignment the system supports (e.g., most systems will only support powers of two, or if it is too high), the result (consequences) will be up to CF or the system to decide + +} CFRuntimeClass; + +#define RADAR_5115468_FIXED 1 + +/* Note that CF runtime class registration and unregistration is not currently + * thread-safe, which should not currently be a problem, as long as unregistration + * is done only when valid to do so. + */ + +CF_EXPORT CFTypeID _CFRuntimeRegisterClass(const CFRuntimeClass * const cls); +/* Registers a new class with the CF runtime. Pass in a + * pointer to a CFRuntimeClass structure. The pointer is + * remembered by the CF runtime -- the structure is NOT + * copied. + * + * - version field must be zero currently. + * - className field points to a null-terminated C string + * containing only ASCII (0 - 127) characters; this field + * may NOT be NULL. + * - init field points to a function which classes can use to + * apply some generic initialization to instances as they + * are created; this function is called by both + * _CFRuntimeCreateInstance and _CFRuntimeInitInstance; if + * this field is NULL, no function is called; the instance + * has been initialized enough that the polymorphic funcs + * CFGetTypeID(), CFRetain(), CFRelease(), CFGetRetainCount(), + * and CFGetAllocator() are valid on it when the init + * function if any is called. + * - copy field should always be NULL. Generic copying of CF + * objects has never been defined (and is unlikely). + * - finalize field points to a function which destroys an + * instance when the retain count has fallen to zero; if + * this is NULL, finalization does nothing. Note that if + * the class-specific functions which create or initialize + * instances more fully decide that a half-initialized + * instance must be destroyed, the finalize function for + * that class has to be able to deal with half-initialized + * instances. The finalize function should NOT destroy the + * memory for the instance itself; that is done by the + * CF runtime after this finalize callout returns. + * - equal field points to an equality-testing function; this + * field may be NULL, in which case only pointer/reference + * equality is performed on instances of this class. + * Pointer equality is tested, and the type IDs are checked + * for equality, before this function is called (so, the + * two instances are not pointer-equal but are of the same + * class before this function is called). + * NOTE: the equal function must implement an immutable + * equality relation, satisfying the reflexive, symmetric, + * and transitive properties, and remains the same across + * time and immutable operations (that is, if equal(A,B) at + * some point, then later equal(A,B) provided neither + * A or B has been mutated). + * - hash field points to a hash-code-computing function for + * instances of this class; this field may be NULL in which + * case the pointer value of an instance is converted into + * a hash. + * NOTE: the hash function and equal function must satisfy + * the relationship "equal(A,B) implies hash(A) == hash(B)"; + * that is, if two instances are equal, their hash codes must + * be equal too. (However, the converse is not true!) + * - copyFormattingDesc field points to a function returning a + * CFStringRef with a human-readable description of the + * instance; if this is NULL, the type does not have special + * human-readable string-formats. + * - copyDebugDesc field points to a function returning a + * CFStringRef with a debugging description of the instance; + * if this is NULL, a simple description is generated. + * + * This function returns _kCFRuntimeNotATypeID on failure, or + * on success, returns the CFTypeID for the new class. This + * CFTypeID is what the class uses to allocate or initialize + * instances of the class. It is also returned from the + * conventional *GetTypeID() function, which returns the + * class's CFTypeID so that clients can compare the + * CFTypeID of instances with that of a class. + * + * The function to compute a human-readable string is very + * optional, and is really only interesting for classes, + * like strings or numbers, where it makes sense to format + * the instance using just its contents. + */ + +CF_EXPORT const CFRuntimeClass * _CFRuntimeGetClassWithTypeID(CFTypeID typeID); +/* Returns the pointer to the CFRuntimeClass which was + * assigned the specified CFTypeID. + */ + +CF_EXPORT void _CFRuntimeUnregisterClassWithTypeID(CFTypeID typeID); +/* Unregisters the class with the given type ID. It is + * undefined whether type IDs are reused or not (expect + * that they will be). + * + * Whether or not unregistering the class is a good idea or + * not is not CF's responsibility. In particular you must + * be quite sure all instances are gone, and there are no + * valid weak refs to such in other threads. + */ + +/* All CF "instances" start with this structure. Never refer to + * these fields directly -- they are for CF's use and may be added + * to or removed or change format without warning. Binary + * compatibility for uses of this struct is not guaranteed from + * release to release. + */ +typedef struct __CFRuntimeBase { + uintptr_t _cfisa; + uint8_t _cfinfo[4]; +#if __LP64__ + uint32_t _rc; +#endif +} CFRuntimeBase; + +#if __BIG_ENDIAN__ +#define INIT_CFRUNTIME_BASE(...) {0, {0, 0, 0, 0x80}} +#else +#define INIT_CFRUNTIME_BASE(...) {0, {0x80, 0, 0, 0}} +#endif + +CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category); +/* Creates a new CF instance of the class specified by the + * given CFTypeID, using the given allocator, and returns it. + * If the allocator returns NULL, this function returns NULL. + * A CFRuntimeBase structure is initialized at the beginning + * of the returned instance. extraBytes is the additional + * number of bytes to allocate for the instance (BEYOND that + * needed for the CFRuntimeBase). If the specified CFTypeID + * is unknown to the CF runtime, this function returns NULL. + * No part of the new memory other than base header is + * initialized (the extra bytes are not zeroed, for example). + * All instances created with this function must be destroyed + * only through use of the CFRelease() function -- instances + * must not be destroyed by using CFAllocatorDeallocate() + * directly, even in the initialization or creation functions + * of a class. Pass NULL for the category parameter. + */ + +CF_EXPORT void _CFRuntimeSetInstanceTypeID(CFTypeRef cf, CFTypeID typeID); +/* This function changes the typeID of the given instance. + * If the specified CFTypeID is unknown to the CF runtime, + * this function does nothing. This function CANNOT be used + * to initialize an instance. It is for advanced usages such + * as faulting. You cannot change the CFTypeID of an object + * of a _kCFRuntimeCustomRefCount class, or to a + * _kCFRuntimeCustomRefCount class. + */ + +CF_EXPORT void _CFRuntimeInitStaticInstance(void *memory, CFTypeID typeID); +/* This function initializes a memory block to be a constant + * (unreleaseable) CF object of the given typeID. + * If the specified CFTypeID is unknown to the CF runtime, + * this function does nothing. The memory block should + * be a chunk of in-binary writeable static memory, and at + * least as large as sizeof(CFRuntimeBase) on the platform + * the code is being compiled for. The init function of the + * CFRuntimeClass is invoked on the memory as well, if the + * class has one. Static instances cannot be initialized to + * _kCFRuntimeCustomRefCount classes. + */ +#define CF_HAS_INIT_STATIC_INSTANCE 1 + +CF_EXTERN_C_END + +#endif /* ! __COREFOUNDATION_CFRUNTIME__ */ diff --git a/Sources/ComputeCxx/Subgraph/AGSubgraph-Private.h b/Sources/ComputeCxx/Subgraph/AGSubgraph-Private.h new file mode 100644 index 0000000..4b0963a --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/AGSubgraph-Private.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "AGSubgraph.h" +#include "Private/CFRuntime.h" +#include "Subgraph.h" + +CF_ASSUME_NONNULL_BEGIN + +struct AGSubgraphStorage { + CFRuntimeBase base; + AG::Subgraph *subgraph; +}; + + + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Subgraph/AGSubgraph.cpp b/Sources/ComputeCxx/Subgraph/AGSubgraph.cpp new file mode 100644 index 0000000..e6340f0 --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/AGSubgraph.cpp @@ -0,0 +1,371 @@ +#include "AGSubgraph-Private.h" + +#include + +#include "Graph/AGGraph-Private.h" +#include "Graph/Context.h" +#include "Graph/Tree/TreeElement.h" +#include "Subgraph.h" + +namespace { + +CFRuntimeClass &subgraph_type_id() { + static auto finalize = [](CFTypeRef subgraph_ref) { + AGSubgraphStorage *storage = (AGSubgraphStorage *)subgraph_ref; + AG::Subgraph *subgraph = storage->subgraph; + if (subgraph) { + // TODO: should call destructor????x + subgraph->clear_object(); + subgraph->invalidate_and_delete_(false); + } + }; + static CFRuntimeClass klass = { + 0, // version + "AGSubgraph", // className + NULL, // init + NULL, // copy, + finalize, + NULL, // equal + NULL, // hash + NULL, // copyFormattingDesc + NULL, // copyDebugDesc, + 0 // ?? + }; + return klass; +} + +} // namespace + +CFTypeID AGSubgraphGetTypeID() { + static CFTypeID type = _CFRuntimeRegisterClass(&subgraph_type_id()); + return type; +} + +AGSubgraphRef AGSubgraphCreate(AGGraphRef graph) { return AGSubgraphCreate2(graph, AGAttributeNil); }; + +AGSubgraphRef AGSubgraphCreate2(AGGraphRef graph, AGAttribute attribute) { + uint32_t extra = sizeof(struct AGSubgraphStorage) - sizeof(CFRuntimeBase); + struct AGSubgraphStorage *instance = + (struct AGSubgraphStorage *)_CFRuntimeCreateInstance(kCFAllocatorDefault, AGSubgraphGetTypeID(), extra, NULL); + if (!instance) { + AG::precondition_failure("memory allocation failure."); + } + + AG::Graph::Context *context = AG::Graph::Context::from_cf(graph); + + instance->subgraph = + new AG::Subgraph((AG::SubgraphObject *)instance, *context, AG::AttributeID::from_storage(attribute)); + ; + return instance; +}; + +#pragma mark - Current subgraph + +AGSubgraphRef AGSubgraphGetCurrent() { + auto current = AG::Subgraph::current_subgraph(); + if (current == nullptr) { + return nullptr; + } + return current->to_cf(); +} + +void AGSubgraphSetCurrent(AGSubgraphRef subgraph) { + // TODO: use util::cf_ptr here? + AG::Subgraph *old_subgraph = AG::Subgraph::current_subgraph(); + if (subgraph != nullptr) { + AG::Subgraph::set_current_subgraph(subgraph->subgraph); + if (subgraph->subgraph != nullptr) { + CFRetain(subgraph); + } + } else { + AG::Subgraph::set_current_subgraph(nullptr); + } + if (old_subgraph && old_subgraph->to_cf()) { + CFRelease(old_subgraph->to_cf()); + } +} + +#pragma mark - Graph + +AGUnownedGraphRef AGSubgraphGetCurrentGraphContext() { + AG::Subgraph *current = AG::Subgraph::current_subgraph(); + if (current == nullptr) { + return nullptr; + } + + AG::Graph *graph = current->graph(); + return reinterpret_cast(graph); +} + +AGGraphRef AGSubgraphGetGraph(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + auto context_id = subgraph->subgraph->context_id(); + if (context_id != 0) { + if (auto context = subgraph->subgraph->graph()->context_with_id(context_id)) { + return AGGraphContextGetGraph(reinterpret_cast(context)); + } + } + + AG::precondition_failure("accessing invalidated context"); +} + +AGSubgraphRef AGGraphGetAttributeSubgraph(AGAttribute attribute) { + auto subgraph = AGGraphGetAttributeSubgraph2(attribute); + if (subgraph == nullptr) { + AG::precondition_failure("no subgraph"); + } + + return subgraph; +} + +AGSubgraphRef AGGraphGetAttributeSubgraph2(AGAttribute attribute) { + auto attribute_id = AG::AttributeID::from_storage(attribute); + attribute_id.validate_data_offset(); + + auto subgraph = attribute_id.subgraph(); + if (subgraph == nullptr) { + AG::precondition_failure("internal error"); + } + + return subgraph->to_cf(); +} + +#pragma mark - Children + +void AGSubgraphAddChild(AGSubgraphRef subgraph, AGSubgraphRef child) { AGSubgraphAddChild2(subgraph, child, 0); } + +void AGSubgraphAddChild2(AGSubgraphRef subgraph, AGSubgraphRef child, uint32_t flags) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + if (child->subgraph != nullptr) { + // TODO: strong type flags + subgraph->subgraph->add_child(*child->subgraph, AG::Subgraph::SubgraphChild::Flags(flags)); + } +} + +void AGSubgraphRemoveChild(AGSubgraphRef subgraph, AGSubgraphRef child) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + if (child->subgraph) { + subgraph->subgraph->remove_child(*child->subgraph, false); + } +} + +uint32_t AGSubgraphGetChildCount(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + return subgraph->subgraph->children().size(); +} + +AGSubgraphRef AGSubgraphGetChild(AGSubgraphRef subgraph, uint32_t index, uint32_t *flags_out) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + if (index >= subgraph->subgraph->children().size()) { + AG::precondition_failure("invalid child index: %u", index); + } + + auto child = subgraph->subgraph->children()[index]; + if (flags_out) { + *flags_out = child.flags(); + } + return child.subgraph()->to_cf(); +} + +uint64_t AGSubgraphGetParentCount(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + return 0; + } + + return subgraph->subgraph->parents().size(); +} + +AGSubgraphRef AGSubgraphGetParent(AGSubgraphRef subgraph, int64_t index) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + if (index >= subgraph->subgraph->parents().size()) { + AG::precondition_failure("invalid parent index: %u", index); + } + + return subgraph->subgraph->parents()[index]->to_cf(); +} + +bool AGSubgraphIsAncestor(AGSubgraphRef subgraph, AGSubgraphRef possible_descendant) { + if (subgraph->subgraph == nullptr) { + return false; + } + + if (possible_descendant->subgraph == nullptr) { + return false; + } + + return subgraph->subgraph->ancestor_of(*possible_descendant->subgraph); +} + +#pragma mark - Attributes + +bool AGSubgraphIsValid(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + return false; + } + + return subgraph->subgraph->is_valid(); +} + +bool AGSubgraphIsDirty(AGSubgraphRef subgraph, uint8_t mask) { + if (subgraph->subgraph == nullptr) { + return false; + } + + return subgraph->subgraph->is_dirty(mask); +} + +bool AGSubgraphIntersects(AGSubgraphRef subgraph, uint8_t mask) { + if (subgraph->subgraph == nullptr) { + return; + } + + return subgraph->subgraph->intersects(mask); +} + +void AGSubgraphInvalidate(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + return; + } + + subgraph->subgraph->invalidate_and_delete_(false); +} + +void AGSubgraphUpdate(AGSubgraphRef subgraph, uint8_t flags) { + if (subgraph->subgraph == nullptr) { + return; + } + + subgraph->subgraph->update(flags); +} + +void AGSubgraphApply(AGSubgraphRef subgraph, AGAttributeFlags flags, + void (*function)(const void *context AG_SWIFT_CONTEXT, AGAttribute) AG_SWIFT_CC(swift), + const void *function_context) { + if (subgraph->subgraph == nullptr) { + return; + } + + subgraph->subgraph->apply(AG::Subgraph::Flags(flags), // TODO: consolidate AGAttributeFlags and AG::Subgraph::Flags + AG::ClosureFunctionAV(function, function_context)); +} + +#pragma mark - Tree + +AGTreeElement AGSubgraphGetTreeRoot(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + return 0; // TODO: nullptr + } + + return AG::TreeElementID(subgraph->subgraph->tree_root()).to_storage(); +} + +void AGSubgraphSetTreeOwner(AGSubgraphRef subgraph, AGAttribute owner) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + subgraph->subgraph->set_tree_owner(AG::AttributeID::from_storage(owner)); +} + +void AGSubgraphAddTreeValue(AGAttribute value, AGTypeID type, const char *key, uint32_t flags) { + AG::Subgraph *current = AG::Subgraph::current_subgraph(); + if (current == nullptr) { + return; + } + + auto metadata = reinterpret_cast(type); + current->add_tree_value(AG::AttributeID::from_storage(value), metadata, key, flags); +} + +void AGSubgraphBeginTreeElement(AGAttribute value, AGTypeID type, uint32_t flags) { + AG::Subgraph *current = AG::Subgraph::current_subgraph(); + if (current == nullptr) { + return; + } + + auto metadata = reinterpret_cast(type); + current->begin_tree(AG::AttributeID::from_storage(value), metadata, flags); +} + +void AGSubgraphEndTreeElement(AGAttribute value) { + AG::Subgraph *current = AG::Subgraph::current_subgraph(); + if (current == nullptr) { + return; + } + + current->end_tree(); +} + +static dispatch_once_t should_record_tree_once = 0; +static bool should_record_tree = true; + +void init_should_record_tree(void *context) { + char *result = getenv("AG_TREE"); + if (result) { + should_record_tree = atoi(result) != 0; + } else { + should_record_tree = false; + } +} + +bool AGSubgraphShouldRecordTree() { + dispatch_once_f(&should_record_tree_once, nullptr, init_should_record_tree); + return should_record_tree; +} + +void AGSubgraphSetShouldRecordTree() { + dispatch_once_f(&should_record_tree_once, nullptr, init_should_record_tree); + should_record_tree = true; +} + +#pragma mark - Observers + +uint64_t AGSubgraphAddObserver(AGSubgraphRef subgraph, + void (*observer)(const void *context AG_SWIFT_CONTEXT) AG_SWIFT_CC(swift), + const void *observer_context) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + return subgraph->subgraph->add_observer(AG::ClosureFunctionVV(observer, observer_context)); +} + +void AGSubgraphRemoveObserver(AGSubgraphRef subgraph, uint64_t observer_id) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + subgraph->subgraph->remove_observer(observer_id); +} + +#pragma mark - Index + +uint32_t AGSubgraphGetIndex(AGSubgraphRef subgraph) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + return subgraph->subgraph->index(); +} + +void AGSubgraphSetIndex(AGSubgraphRef subgraph, uint32_t index) { + if (subgraph->subgraph == nullptr) { + AG::precondition_failure("accessing invalidated subgraph"); + } + + subgraph->subgraph->set_index(index); +} diff --git a/Sources/ComputeCxx/Subgraph/AGSubgraph.h b/Sources/ComputeCxx/Subgraph/AGSubgraph.h index b3a8081..20dab3e 100644 --- a/Sources/ComputeCxx/Subgraph/AGSubgraph.h +++ b/Sources/ComputeCxx/Subgraph/AGSubgraph.h @@ -2,12 +2,169 @@ #include +#include "Attribute/AGAttribute.h" +#include "Graph/AGGraph.h" +#include "Graph/Tree/AGTreeElement.h" + CF_ASSUME_NONNULL_BEGIN CF_EXTERN_C_BEGIN typedef struct CF_BRIDGED_TYPE(id) AGSubgraphStorage *AGSubgraphRef CF_SWIFT_NAME(Subgraph); +CF_EXPORT +CF_REFINED_FOR_SWIFT +CFTypeID AGSubgraphGetTypeID(); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGSubgraphCreate(AGGraphRef graph) CF_SWIFT_NAME(AGSubgraphRef.init(graph:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGSubgraphCreate2(AGGraphRef graph, AGAttribute attribute) + CF_SWIFT_NAME(AGSubgraphRef.init(graph:attribute:)); + +// MARK: Current subgraph + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef _Nullable AGSubgraphGetCurrent() CF_SWIFT_NAME(getter:AGSubgraphRef.current()); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphSetCurrent(AGSubgraphRef _Nullable subgraph) CF_SWIFT_NAME(setter:AGSubgraphRef.current(_:)); + +// MARK: Graph + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGUnownedGraphRef _Nullable AGSubgraphGetCurrentGraphContext() + CF_SWIFT_NAME(getter:AGSubgraphRef.currentGraphContext()); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGGraphRef AGSubgraphGetGraph(AGSubgraphRef subgraph) CF_SWIFT_NAME(getter:AGSubgraphRef.graph(self:)); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGGraphGetAttributeSubgraph(AGAttribute attribute); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGGraphGetAttributeSubgraph2(AGAttribute attribute); + +// MARK: Children + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphAddChild(AGSubgraphRef subgraph, AGSubgraphRef child); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphAddChild2(AGSubgraphRef subgraph, AGSubgraphRef child, uint32_t flags); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphRemoveChild(AGSubgraphRef subgraph, AGSubgraphRef child); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint32_t AGSubgraphGetChildCount(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGSubgraphGetChild(AGSubgraphRef subgraph, uint32_t index, uint32_t *flags_out); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +uint64_t AGSubgraphGetParentCount(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGSubgraphRef AGSubgraphGetParent(AGSubgraphRef subgraph, int64_t index); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGSubgraphIsAncestor(AGSubgraphRef subgraph, AGSubgraphRef possible_descendant); + +// MARK: Attributes + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGSubgraphIsValid(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGSubgraphIsDirty(AGSubgraphRef subgraph, uint8_t mask); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGSubgraphIntersects(AGSubgraphRef subgraph, uint8_t mask); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphInvalidate(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphUpdate(AGSubgraphRef subgraph, uint8_t flags); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphApply(AGSubgraphRef subgraph, AGAttributeFlags flags, + void (*function)(const void *context AG_SWIFT_CONTEXT, AGAttribute) AG_SWIFT_CC(swift), + const void *function_context); + +// MARK: Tree + +CF_EXPORT +CF_REFINED_FOR_SWIFT +AGTreeElement AGSubgraphGetTreeRoot(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphSetTreeOwner(AGSubgraphRef subgraph, AGAttribute owner); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphAddTreeValue(AGAttribute value, AGTypeID type, const char *key, uint32_t flags); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphBeginTreeElement(AGAttribute value, AGTypeID type, uint32_t flags); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphEndTreeElement(AGAttribute value); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +bool AGSubgraphShouldRecordTree() CF_SWIFT_NAME(getter:AGSubgraphRef.shouldRecordTree()); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphSetShouldRecordTree() CF_SWIFT_NAME(AGSubgraphRef.setShouldRecordTree()); + +// MARK: Observers + +CF_EXPORT CF_REFINED_FOR_SWIFT uint64_t AGSubgraphAddObserver(AGSubgraphRef subgraph, + void (*observer)(const void *context AG_SWIFT_CONTEXT) + AG_SWIFT_CC(swift), + const void *observer_context); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphRemoveObserver(AGSubgraphRef subgraph, uint64_t observer_id); + +// MARK: Index + +CF_EXPORT CF_REFINED_FOR_SWIFT uint32_t AGSubgraphGetIndex(AGSubgraphRef subgraph); + +CF_EXPORT +CF_REFINED_FOR_SWIFT +void AGSubgraphSetIndex(AGSubgraphRef subgraph, uint32_t index); + CF_EXTERN_C_END CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Subgraph/NodeCache.cpp b/Sources/ComputeCxx/Subgraph/NodeCache.cpp new file mode 100644 index 0000000..58aa2de --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/NodeCache.cpp @@ -0,0 +1,53 @@ +#include "NodeCache.h" + +#include "Attribute/AttributeType.h" +#include "Swift/SwiftShims.h" + +namespace AG { + +Subgraph::NodeCache::NodeCache() noexcept + : _heap(nullptr, 0, 0), _types(nullptr, nullptr, nullptr, nullptr, &_heap), + _table2([](const ItemKey *item) -> uint64_t { return item->field_0x00 >> 8; }, + [](const ItemKey *a, const ItemKey *b) -> bool { + if (a == b) { + return true; + } + + if (a->equals_item != b->equals_item || (a->field_0x00 ^ b->field_0x00) > 0xff) { + return false; + } + + void *a_body = nullptr; + if (a->node) { + data::ptr a_node = a->node; + auto a_type = AttributeID(a->node).subgraph()->graph()->attribute_type(a_node->type_id()); + a_body = a_node->get_self(a_type); + } else { + a_body = a->body; // next or body? + } + + void *b_body = nullptr; + if (b->node) { + data::ptr b_node = b->node; + auto b_type = AttributeID(b->node).subgraph()->graph()->attribute_type(b_node->type_id()); + b_body = b_node->get_self(b_type); + } else { + b_body = b->body; // next or body? + } + + return AGDispatchEquatable(a_body, b_body, a->equals_item->type, a->equals_item->equatable); + }, + nullptr, nullptr, &_heap), + _items(nullptr, nullptr, nullptr, nullptr, &_heap) {} + +Subgraph::NodeCache::~NodeCache() noexcept { + _items.for_each( + [](data::ptr node, NodeCache::Item *item, void *context) { + if (item) { + delete item; + } + }, + nullptr); +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Subgraph/NodeCache.h b/Sources/ComputeCxx/Subgraph/NodeCache.h new file mode 100644 index 0000000..f9c8112 --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/NodeCache.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include "Subgraph.h" +#include "Swift/Metadata.h" +#include "Utilities/HashTable.h" +#include "Utilities/Heap.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Subgraph::NodeCache { + public: + struct Item; + struct Type { + const swift::metadata *type; + const swift::equatable_witness_table *equatable; + + // doubly-linked list + Item *last_item; + Item *first_item; + + uint32_t type_id; + }; + static_assert(sizeof(Type) == 0x28); + struct Item { + uint64_t field_0x00; + data::ptr equals_item; + data::ptr node; + Item *prev; + Item *next; + }; + static_assert(sizeof(Item) == 0x20); + struct ItemKey { + uint64_t field_0x00; + data::ptr equals_item; + data::ptr node; + void *body; + }; + + private: + os_unfair_lock _lock; // can't find if this is used + util::Heap _heap; + util::Table> _types; + util::Table _table2; + util::Table, Item *> _items; + + public: + NodeCache() noexcept; + ~NodeCache() noexcept; + + util::Table> &types() { return _types; }; + util::Table &table2() { return _table2; }; + util::Table, Item *> &items() { return _items; }; +}; + +}; // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Subgraph/Subgraph.cpp b/Sources/ComputeCxx/Subgraph/Subgraph.cpp new file mode 100644 index 0000000..692d9b5 --- /dev/null +++ b/Sources/ComputeCxx/Subgraph/Subgraph.cpp @@ -0,0 +1,1140 @@ +#include "Subgraph.h" + +#include + +#include "AGSubgraph-Private.h" +#include "Attribute/AttributeIDList.h" +#include "Attribute/AttributeType.h" +#include "Attribute/Node/IndirectNode.h" +#include "Attribute/Node/Node.h" +#include "Attribute/OffsetAttributeID.h" +#include "Encoder/Encoder.h" +#include "Errors/Errors.h" +#include "Graph/AGGraph-Private.h" +#include "Graph/Context.h" +#include "Graph/Graph.h" +#include "Graph/Tree/TreeElement.h" +#include "Graph/UpdateStack.h" +#include "NodeCache.h" +#include "Trace/Trace.h" +#include "UniqueID/AGUniqueID.h" +#include "Utilities/CFPointer.h" + +namespace AG { + +pthread_key_t Subgraph::_current_subgraph_key; + +void Subgraph::make_current_subgraph_key() { pthread_key_create(&Subgraph::_current_subgraph_key, 0); } + +Subgraph *Subgraph::current_subgraph() { + assert(Subgraph::_current_subgraph_key); + return (Subgraph *)pthread_getspecific(Subgraph::_current_subgraph_key); +} + +void Subgraph::set_current_subgraph(Subgraph *subgraph) { + pthread_setspecific(Subgraph::_current_subgraph_key, subgraph); +} + +Subgraph::Subgraph(SubgraphObject *object, Graph::Context &context, AttributeID owner) { + _object = object; + + Graph *graph = &context.graph(); + _graph = graph; + _context_id = context.unique_id(); + + // what is this check doing? + if ((uintptr_t)this == 1) { + print(0); + graph = _graph; + } + + graph->add_subgraph(*this); + + if (AGSubgraphShouldRecordTree()) { + if (!owner.has_value()) { + auto update = Graph::current_update(); + if (update.tag() == 0 && update.get() != nullptr) { + if (auto top = update.get()->global_top()) { + owner = AttributeID(top->attribute); + } + } + } + begin_tree(owner, nullptr, 0); + } + + context.graph().foreach_trace([this](Trace &trace) { + trace.created(*this); + // fprintf(stdout, "trace created subgraph ref"); + }); +} + +Subgraph::~Subgraph() { + if (_observers) { + notify_observers(); + delete _observers.get(); // TODO: what pointer is deleted? + } + if (_cache) { + _cache->~NodeCache(); + } +} + +#pragma mark - CoreFoundation + +Subgraph *Subgraph::from_cf(AGSubgraphStorage *storage) { return storage->subgraph; } + +AGSubgraphStorage *Subgraph::to_cf() const { return reinterpret_cast(_object); } + +void Subgraph::clear_object() { + auto object = _object; + if (object) { + object->clear_subgraph(); + _object = nullptr; + + if (current_subgraph() == this) { + set_current_subgraph(nullptr); + CFRelease(object); + } + } +} + +#pragma mark - Graph + +void Subgraph::invalidate_and_delete_(bool delete_subgraph) { + if (delete_subgraph) { + mark_deleted(); + } + + // TODO: does this rely on underflow to catch 0? + if (ValidationState::Invalidated < _validation_state - 1) { + for (auto parent : _parents) { + parent->remove_child(*this, true); + } + _parents.clear(); + + // Check Graph::invalidate_subgraphs + if (_graph->is_deferring_subgraph_invalidation() == false && _graph->main_handler() == nullptr) { + invalidate_now(*_graph); + _graph->invalidate_subgraphs(); + return; + } + + bool was_valid = is_valid(); + if (_validation_state != ValidationState::InvalidationScheduled) { + _graph->will_invalidate_subgraph(*this); + _validation_state = ValidationState::InvalidationScheduled; + if (was_valid) { + _graph->foreach_trace([this](Trace &trace) { trace.invalidate(*this); }); + } + } + } +} + +void Subgraph::invalidate_now(Graph &graph) { + // TODO: double check graph param vs _graph instance var + + graph.set_deferring_subgraph_invalidation(true); + + auto removed_subgraphs = vector(); + auto stack = std::stack>(); + + bool was_valid = is_valid(); + if (_validation_state != ValidationState::Invalidated) { + _validation_state = ValidationState::Invalidated; + if (was_valid) { + _graph->foreach_trace([this](Trace &trace) { trace.invalidate(*this); }); + } + clear_object(); + stack.push(this); + + while (!stack.empty()) { + Subgraph *subgraph = stack.top(); + stack.pop(); + + _graph->foreach_trace([subgraph](Trace &trace) { trace.destroy(*subgraph); }); + + notify_observers(); + _graph->remove_subgraph(*subgraph); + + subgraph->mark_deleted(); + removed_subgraphs.push_back(subgraph); + + for (auto child : subgraph->_children) { + Subgraph *child_subgraph = child.subgraph(); + if (child_subgraph->context_id() == _context_id) { + + // for each other parent of the child, remove the child from that parent + for (auto parent : child_subgraph->_parents) { + if (parent != subgraph) { + auto r = std::remove_if(parent->_children.begin(), parent->_children.end(), + [child_subgraph](auto other_child) { + return other_child.subgraph() == child_subgraph; + }); + parent->_children.erase(r); + + // for (auto other_child : parent->_children) { + // if (other_child.subgraph() == child) { + // parent->_children[i] = + // parent->_children[parent->_children.size() - 1]; + // parent->_children.pop_back(); + // } + // } + } + } + + child_subgraph->_parents.clear(); + + bool child_was_valid = child_subgraph->is_valid(); + if (child_subgraph->_validation_state != ValidationState::Invalidated) { + child_subgraph->_validation_state = ValidationState::Invalidated; + if (child_was_valid) { + _graph->foreach_trace( + [child_subgraph](Trace &trace) { trace.invalidate(*child_subgraph); }); + } + child_subgraph->clear_object(); + stack.push(child_subgraph); + } + } else { + // remove the subgraph from the parents vector of each child + auto r = std::remove(child_subgraph->_parents.begin(), child_subgraph->_parents.end(), subgraph); + child_subgraph->_parents.erase(r); + + // for (auto parent : child_subgraph->_parents) { + // if (parent == subgraph) { + // child._parents[i] = child._parents[child._parents.size() - 1]; + // child._parents.resize(child._parents.size() - 1); + // break; + // } + // } + } + } + } + } + + for (auto removed_subgraph : removed_subgraphs) { + for (auto page : removed_subgraph->pages()) { + bool found_nil_attribute = false; + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_direct()) { + graph.remove_node(attribute.to_ptr()); // TODO: does this muck up iteration + } else if (attribute.is_indirect()) { + graph.remove_indirect_node(attribute.to_ptr()); // TODO: does this muck up iteration + } else if (attribute.is_nil()) { + found_nil_attribute = true; + } + } + if (found_nil_attribute) { + break; + } + } + } + + for (auto removed_subgraph : removed_subgraphs) { + for (auto page : removed_subgraph->pages()) { + bool found_nil_attribute = false; + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_direct()) { + attribute.to_node().destroy(*_graph); // TODO: does this muck up iteration + + _graph->did_destroy_node(); // decrement counter + } else if (attribute.is_nil()) { + found_nil_attribute = true; + } + } + if (found_nil_attribute) { + break; + } + } + } + + // TODO: does this execute anyway... + for (auto removed_subgraph : removed_subgraphs) { + removed_subgraph->~Subgraph(); + free(removed_subgraph); // or delete? + } + + graph.set_deferring_subgraph_invalidation(false); +} + +void Subgraph::graph_destroyed() { + bool was_valid = is_valid(); + _validation_state = ValidationState::GraphDestroyed; + + if (was_valid) { + graph()->foreach_trace([this](Trace &trace) { trace.invalidate(*this); }); + } + notify_observers(); + + for (auto page : pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_direct()) { + attribute.to_node().destroy(*_graph); // TODO: does this muck up iteration? + } else if (attribute.is_nil()) { + break; // TODO: check if this should break out of entire loop + } + } + } + + _parents.clear(); + _children.clear(); + clear(); +} + +#pragma mark - Managing children + +void Subgraph::add_child(Subgraph &child, SubgraphChild::Flags flags) { + if (child.graph() != graph()) { + precondition_failure("child subgraph must have same graph"); + } + for (auto parent : child._parents) { + if (parent == this) { + precondition_failure("child already attached to new parent"); + } + } + graph()->foreach_trace([this, &child](Trace &trace) { trace.add_child(*this, child); }); + _children.push_back(SubgraphChild(&child, flags)); + + uint8_t new_flags = child._flags.value1 | child._flags.value2; + if (new_flags & ~_flags.value2) { + _flags.value2 |= new_flags; // value2 so can't call add_flags + propagate_flags(); + } + + uint8_t dirty_flags = child._flags.value3 | child._flags.value4; + if (dirty_flags & ~_flags.value4) { + _flags.value4 |= dirty_flags; // value4 so can't call add_dirty_flags + propagate_dirty_flags(); + } + + child._parents.push_back(this); +} + +void Subgraph::remove_child(Subgraph &child, bool without_trace) { + auto parents_end = std::remove(child._parents.begin(), child._parents.end(), this); + child._parents.erase(parents_end, child._parents.end()); + + if (!without_trace) { + graph()->foreach_trace([this, &child](Trace &trace) { trace.remove_child(*this, child); }); + } + + auto children_end = std::remove(_children.begin(), _children.end(), &child); + _children.erase(children_end, _children.end()); +} + +bool Subgraph::ancestor_of(const Subgraph &other) { + auto other_parents = std::stack>(); + const Subgraph *candidate = &other; + while (true) { + if (candidate == nullptr) { + // previous candidate was top level + if (other_parents.empty()) { + return false; + } + candidate = other_parents.top(); + other_parents.pop(); + } + + if (candidate == this) { + return true; + } + + candidate = candidate->_parents.empty() ? nullptr : candidate->_parents.front(); + for (auto iter = other._parents.begin() + 1, end = other._parents.end(); iter != end; ++iter) { + auto parent = *iter; + other_parents.push(parent); + } + } +} + +template + requires std::invocable && std::same_as, bool> +void Subgraph::foreach_ancestor(Callable body) { + // Status: Verified + for (auto iter = _parents.rbegin(), end = _parents.rend(); iter != end; ++iter) { + auto parent = *iter; + if (body(*parent)) { + parent->foreach_ancestor(body); + } + } +} + +#pragma mark - Attributes + +void Subgraph::add_node(data::ptr node) { + node->set_subgraph_flags(0); // TODO: check value + insert_attribute(AttributeID(node), true); + + if (_tree_root) { + auto &tree_data_element = graph()->tree_data_element_for_subgraph(this); + tree_data_element.push_back({ + _tree_root, + node, + }); + } + + graph()->foreach_trace([&node](Trace &trace) { trace.added(node); }); +} + +void Subgraph::add_indirect(data::ptr node, bool flag) { + insert_attribute(AttributeID(node), flag); // make sure adds Indirect kind to node + + graph()->foreach_trace([&node](Trace &trace) { trace.added(node); }); +} + +void Subgraph::insert_attribute(AttributeID attribute, bool after_flagged_nodes) { + AttributeID before_attribute = AttributeIDNil; + + if (after_flagged_nodes) { + if (!attribute.is_direct() || attribute.to_node().subgraph_flags() == 0) { + for (auto candidate_attribute : AttributeIDList1(attribute.page_ptr())) { + if (!candidate_attribute.is_direct() || candidate_attribute.to_node().subgraph_flags() == 0) { + break; + } + before_attribute = candidate_attribute; + } + } + } + + RelativeAttributeID inserted_offset = attribute.to_relative(); + RelativeAttributeID next_offset; + if (before_attribute.is_direct()) { + next_offset = before_attribute.to_node().relative_offset(); + before_attribute.to_node().set_relative_offset(inserted_offset); + } else if (before_attribute.is_indirect()) { + next_offset = before_attribute.to_indirect_node().relative_offset(); + before_attribute.to_indirect_node().set_relative_offset(inserted_offset); + } else { + if (after_flagged_nodes) { + next_offset = attribute.page_ptr()->first_child_1; + attribute.page_ptr()->first_child_1 = inserted_offset.value(); + } else { + next_offset = attribute.page_ptr()->first_child_2; + attribute.page_ptr()->first_child_2 = inserted_offset.value(); + } + } + + if (attribute.is_direct()) { + attribute.to_node().set_relative_offset(next_offset); + } else if (attribute.is_indirect()) { + attribute.to_indirect_node().set_relative_offset(next_offset); + } +} + +void Subgraph::unlink_attribute(AttributeID attribute) { + // Find the attribute before the given attribute + // TODO: what happens if attribute is not found + AttributeID previous_attribute = AttributeIDNil; + for (auto candidate_attribute : AttributeIDList1(attribute.page_ptr())) { + if (candidate_attribute.is_nil()) { + break; + } + if (candidate_attribute == attribute) { + break; + } + previous_attribute = candidate_attribute; + } + + RelativeAttributeID old_value = RelativeAttributeID(); + if (attribute.is_direct()) { + old_value = attribute.to_node().relative_offset(); + attribute.to_node().set_relative_offset(nullptr); + } else { + old_value = attribute.to_indirect_node().relative_offset(); + attribute.to_indirect_node().set_relative_offset(nullptr); + } + + if (previous_attribute.is_direct()) { + previous_attribute.to_node().set_relative_offset(old_value); + } else if (previous_attribute.is_indirect()) { + previous_attribute.to_indirect_node().set_relative_offset(old_value); + } else { + attribute.page_ptr()->first_child_1 = old_value.value(); + } +} + +void Subgraph::update(uint8_t flags) { + // TODO: redo this method + + if (_graph->needs_update()) { + if (!_graph->thread_is_updating()) { + _graph->call_update(); + } + } + + if (is_valid()) { + if ((flags & (_flags.value1 | _flags.value2))) { + + _graph->foreach_trace([this, &flags](Trace &trace) { trace.begin_update(*this, flags); }); + + _last_traversal_seed += 1; // TODO: check atomics + + auto stack = std::stack, + AG::vector, 32, uint64_t>>(); + auto nodes_to_update = vector, 256, uint64_t>(); + + stack.push(util::cf_ptr(to_cf())); + _traversal_seed = _last_traversal_seed; + + bool thread_is_updating = false; + while (!stack.empty()) { + + util::cf_ptr object = stack.top(); + stack.pop(); + + Subgraph *subgraph = Subgraph::from_cf(object.get()); + if (subgraph) { + + while (subgraph->is_valid()) { + if ((flags & subgraph->_flags.value3) == 0) { + // LABEL: LAB_1afe6ac70 + + if (flags & subgraph->_flags.value4) { + subgraph->_flags.value4 &= ~flags; + for (auto child : subgraph->_children) { + Subgraph *child_subgraph = child.subgraph(); + // TODO: check child has 0x3 pointer tag that needs to be masked... + if (flags & (child_subgraph->_flags.value3 | child_subgraph->_flags.value4) && + child_subgraph->_traversal_seed != _traversal_seed) { + stack.push(util::cf_ptr(child_subgraph->to_cf())); + child_subgraph->_traversal_seed = _traversal_seed; + } + } + } + break; + } + + uint8_t old_flags_1 = subgraph->_flags.value1; + subgraph->_flags.value3 &= ~flags; + if (flags == 0 || (old_flags_1 & flags)) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_direct()) { + auto node = attribute.to_node(); + if (flags) { + if (node.subgraph_flags() == 0) { + break; + } + if ((node.subgraph_flags() & flags) == 0) { + continue; + } + } + if (node.state().is_dirty()) { + nodes_to_update.push_back(attribute.to_ptr()); + } + } else if (attribute.is_indirect()) { + if (flags) { + break; + } + } + } + } + } + + if (nodes_to_update.size() == 0) { + if (subgraph->is_valid()) { + // goto LAB_1afe6ac70 + } + break; + } + + for (auto node : nodes_to_update) { + if (!thread_is_updating) { + _graph->increment_transaction_count_if_needed(); + _graph->update_attribute(AttributeID(node), true); + + if (!subgraph->is_valid()) { + break; + } + } + } + nodes_to_update.clear(); + } + + _graph->invalidate_subgraphs(); + } + + // CFRelease happens automatically + + } // while !stack.empty + + _graph->invalidate_subgraphs(); + _graph->foreach_trace([this](Trace &trace) { trace.end_update(*this); }); + } + } +} + +#pragma mark - Traversal + +std::atomic Subgraph::_last_traversal_seed = {}; + +void Subgraph::apply(Flags flags, ClosureFunctionAV body) { + // Status: Verified, needs checks for atomics + if (!is_valid()) { + return; + } + if ((flags.value1 & (_flags.value1 | _flags.value2)) == 0) { + return; + } + + // Defer invalidation until the end of this method's scope + auto without_invalidating = Graph::without_invalidating(graph()); + + _last_traversal_seed += 1; // TODO: check atomics + + auto stack = std::stack>(); + + stack.push(this); + _traversal_seed = _last_traversal_seed; + + while (!stack.empty()) { + auto subgraph = stack.top(); + stack.pop(); + + // TODO: check + if (!subgraph->is_valid()) { + continue; + } + + if (flags.value4 & 1 || subgraph->context_id() == _context_id) { + if (flags.is_null() || (flags.value1 & subgraph->_flags.value1)) { + for (auto page : subgraph->pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (attribute.is_nil()) { + break; // TODO: check if this should break out of entire loop + } + if (attribute.is_direct()) { + if (!flags.is_null()) { + if (attribute.to_node().subgraph_flags() == 0) { + break; + } + if (flags.value3 & (attribute.to_node().subgraph_flags() == 0)) { + continue; + } + } + + body(attribute); + } else if (attribute.is_indirect()) { + if (!flags.is_null()) { + break; + } + } + } + } + } + + for (auto child : subgraph->_children) { + Subgraph *child_subgraph = child.subgraph(); + if (flags.value1 & (child_subgraph->_flags.value1 | child_subgraph->_flags.value2) && + child_subgraph->_traversal_seed != _last_traversal_seed) { + stack.push(child_subgraph); + child_subgraph->_traversal_seed = _last_traversal_seed; + } + } + } + } + + // ~without_invalidating(); +} + +// MARK: - Tree + +void Subgraph::begin_tree(AttributeID value, const swift::metadata *type, uint32_t flags) { + + data::ptr tree = alloc_bytes(sizeof(Graph::TreeElement), 7).unsafe_cast(); + tree->type = type; + tree->node = value; + tree->flags = flags; + tree->parent = _tree_root; + tree->next_sibling = nullptr; + + auto old_root = _tree_root; + _tree_root = tree; + + if (old_root) { + _tree_root->next_sibling = old_root->first_child; + old_root->first_child = _tree_root; + } +} + +void Subgraph::end_tree() { + if (_tree_root && _tree_root->parent) { + _tree_root = _tree_root->parent; + } +} + +void Subgraph::set_tree_owner(AttributeID attribute) { + if (_tree_root) { // TODO: bug? + return; + } + if (_tree_root->parent) { + precondition_failure("setting owner of non-root tree"); + } + _tree_root->node = attribute; +} + +void Subgraph::add_tree_value(AttributeID value, const swift::metadata *type, const char *key, uint32_t flags) { + if (!_tree_root) { + return; + } + + auto key_id = graph()->intern_key(key); + + data::ptr tree_value = alloc_bytes(sizeof(Graph::TreeValue), 7).unsafe_cast(); + tree_value->type = type; + tree_value->value = value; + tree_value->key_id = key_id; + tree_value->flags = flags; + tree_value->next = _tree_root->first_value; + + _tree_root->first_value = tree_value; +} + +/// Returns the node after the given tree element. +AttributeID Subgraph::tree_node_at_index(data::ptr tree_element, uint64_t index) { + if (auto map = graph()->tree_data_elements()) { + auto tree_data_element = map->find(this); + if (tree_data_element != map->end()) { + tree_data_element->second.sort_nodes(); + + auto &nodes = tree_data_element->second.nodes(); + std::pair, data::ptr> *found = std::find_if( + nodes.begin(), nodes.end(), [&tree_element](auto node) { return node.first == tree_element; }); + + uint64_t i = index; + for (auto node = found; node != nodes.end(); ++node) { + if (node->first != tree_element) { + break; + } + if (i == 0) { + return AttributeID(node->second); + } + --i; + } + } + } + return AttributeIDNil; +} + +data::ptr Subgraph::tree_subgraph_child(data::ptr tree_element) { + auto map = _graph->tree_data_elements(); + if (!map) { + return nullptr; + } + auto tree_data_element = map->find(this); + tree_data_element->second.sort_nodes(); + + auto &nodes = tree_data_element->second.nodes(); + if (nodes.empty()) { + return nullptr; + } + + // TODO: verify this is lower_bound + auto iter = std::lower_bound(nodes.begin(), nodes.end(), tree_element, + [](auto iter, auto value) -> bool { return iter.first.offset() < value.offset(); }); + if (iter == nodes.end()) { + return; + } + + auto subgraph_vector = vector(); + + for (auto subgraph : _graph->subgraphs()) { + if (!subgraph->is_valid()) { + continue; + } + if (subgraph->_tree_root == nullptr) { + continue; + } + AttributeID attribute = subgraph->_tree_root->node; + if (!attribute.has_value()) { + continue; + } + OffsetAttributeID resolved = attribute.resolve(TraversalOptions::None); + attribute = resolved.attribute(); + if (attribute.is_direct()) { + for (auto node_iter = iter; node_iter != nodes.end(); ++node_iter) { + if (node_iter->first->node == attribute) { + subgraph_vector.push_back(subgraph); + } + } + } + } + + std::sort(subgraph_vector.begin(), subgraph_vector.end()); + + // TODO: not sure what is happening here + auto result = data::ptr(); + auto last_old_parent = data::ptr(); + for (auto subgraph : subgraph_vector) { + result = subgraph->_tree_root; + subgraph->_tree_root->next_sibling = last_old_parent; + last_old_parent = result; + } + return result; +} + +// MARK: - Flags + +void Subgraph::set_flags(data::ptr node, AGAttributeFlags flags) { + if (node->subgraph_flags() == flags) { + return; + } + if (node->subgraph_flags() == 0 || flags == 0) { + // potentially reorder + unlink_attribute(AttributeID(node)); + node->set_subgraph_flags(flags); + insert_attribute(AttributeID(node), true); + } else { + node->set_subgraph_flags(flags); + } + + add_flags(flags); + if (node->state().is_dirty()) { + add_dirty_flags(flags); + } +} + +void Subgraph::add_flags(AGAttributeFlags flags) { + // Status: doesn't exist in decompile + if (flags & ~_flags.value1) { + _flags.value1 |= flags; + propagate_flags(); + } +} + +void Subgraph::add_dirty_flags(AGAttributeFlags dirty_flags) { + // Status: Verified + if (dirty_flags & ~_flags.value3) { + _flags.value3 |= dirty_flags; + propagate_dirty_flags(); + } +} + +void Subgraph::propagate_flags() { + // Status: doesn't exist so not sure if new_flags is _flags.value1 | _flags.value2; + uint8_t new_flags = _flags.value1 | _flags.value2; + foreach_ancestor([&new_flags](Subgraph &ancestor) -> bool { + if (new_flags & ~ancestor._flags.value2) { + ancestor._flags.value2 |= new_flags; + return true; + } + return false; + }); +} + +void Subgraph::propagate_dirty_flags() { + // Status: Verified + uint8_t arg = _flags.value3 | _flags.value4; + foreach_ancestor([&arg](Subgraph &ancestor) -> bool { + if (arg & ancestor._flags.value4) { + ancestor._flags.value4 |= arg; + return true; + } + return false; + }); +} + +// TODO: inline +bool Subgraph::is_dirty(uint8_t mask) const { return ((_flags.value3 | _flags.value4) & mask) != 0; } + +bool Subgraph::intersects(uint8_t mask) const { return ((_flags.value1 | _flags.value2) & mask) != 0; } + +// MARK: - Observers + +uint64_t Subgraph::add_observer(ClosureFunctionVV &&callback) { + if (!_observers) { + _observers = + alloc_bytes(sizeof(vector *), 7).unsafe_cast *>(); + *_observers = new vector(); + } + + auto observer_id = AGMakeUniqueID(); + + auto observer = Observer(callback, observer_id); + (*_observers)->push_back(observer); + return observer_id; +} + +void Subgraph::remove_observer(uint64_t observer_id) { + if (auto observers_ptr = _observers.get()) { + auto observers = *observers_ptr; + auto iter = std::remove_if(observers->begin(), observers->end(), [&observer_id](auto observer) -> bool { + if (observer.observer_id == observer_id) { + return true; + } + return false; + }); + observers->erase(iter); + } +} + +void Subgraph::notify_observers() { + if (auto observers_ptr = _observers.get()) { + auto observers = *observers_ptr; + while (!observers->empty()) { + auto &observer = observers->back(); + observer.callback(); + observers->pop_back(); + } + } +} + +#pragma mark - Cache + +data::ptr Subgraph::cache_fetch(uint64_t identifier, const swift::metadata &metadata, void *body, + ClosureFunctionCI closure) { + if (_cache == nullptr) { + _cache = alloc_bytes(sizeof(NodeCache), 7).unsafe_cast(); + new (&_cache) NodeCache(); + } + + auto type = _cache->types().lookup(&metadata, nullptr); + if (type == nullptr) { + auto equatable = metadata.equatable(); + if (equatable == nullptr) { + precondition_failure("cache key must be equatable: %s", metadata.name(false)); + } + + type = alloc_bytes(sizeof(NodeCache::Type), 7).unsafe_cast(); + type->type = &metadata; + type->equatable = equatable; + type->last_item = nullptr; + type->first_item = nullptr; + type->type_id = closure(reinterpret_cast(_graph)); + _cache->types().insert(&metadata, type); + } + + NodeCache::ItemKey item_lookup_key = {identifier << 8, type, nullptr, body}; + NodeCache::Item *item = _cache->table2().lookup(&item_lookup_key, nullptr); + if (item == nullptr) { + if (!closure) { + return nullptr; + } + + item = type->first_item; + if (item == 0 || item->field_0x00 < 2) { + data::ptr node = graph()->add_attribute(*this, type->type_id, body, nullptr); + + item = new NodeCache::Item(item_lookup_key.field_0x00, item_lookup_key.equals_item, node, nullptr, nullptr); + + node->flags().set_cacheable(true); + + _cache->items().insert(node, item); + } else { + // remove item from linked list + if (item->prev != nullptr) { + item->prev->next = item->next; + } else { + type->first_item = item->next; + } + if (item->next != nullptr) { + item->next->prev = item->prev; + } else { + type->last_item = item->prev; + } + + if (item->field_0x00 != 0xff) { + _cache->table2().remove_ptr((const NodeCache::ItemKey *)item); + } + + data::ptr node = item->node; + _graph->remove_all_inputs(node); + node->set_state(node->state().with_dirty(true).with_pending(true)); + node->update_self(*_graph, body); + item->field_0x00 |= identifier << 8; + } + + _cache->table2().insert((const NodeCache::ItemKey *)item, item); + } else { + if (item->field_0x00 & 0xff) { + // remove item from linked list + if (item->prev != nullptr) { + item->prev->next = item->next; + } else { + type->first_item = item->next; + } + if (item->next != nullptr) { + item->next->prev = item->prev; + } else { + type->last_item = item->prev; + } + } + } + item->field_0x00 &= 0xffffffffffffff00; + return item->node; +} + +void Subgraph::cache_insert(data::ptr node) { + if (!is_valid()) { + return; + } + + if (node->outputs().empty() && node->flags().cacheable() && !node->state().is_updating()) { + // TODO: one of these flags must indicate it is cached + + const AttributeType &attribute_type = _graph->attribute_type(node->type_id()); + data::ptr type = _cache->types().lookup(&attribute_type.self_metadata(), nullptr); + + NodeCache::Item *item = _cache->items().lookup(node, nullptr); + item->field_0x00 += 1; + + // insert into linked list + item->prev = type->last_item; + item->next = nullptr; + type->last_item = item; + if (item->prev != nullptr) { + item->prev->next = item; + } else { + type->first_item = item; + } + + if ((_other_state & CacheState::Option1) == 0) { + if ((_other_state & CacheState::Option2) == 0) { + _graph->add_subgraphs_with_cached_node(this); + } + _other_state |= CacheState::Option1; + } + } +} + +void Subgraph::cache_collect() { + _other_state &= ~CacheState::Option1; // turn off 0x1 bit + + std::pair context = {this, _cache.get()}; + if (_cache != nullptr && is_valid()) { + _cache->types().for_each( + [](const swift::metadata *metadata, const data::ptr type, void *context) { + Subgraph *subgraph = reinterpret_cast *>(context)->first; + NodeCache *cache = reinterpret_cast *>(context)->second; + + NodeCache::Item *item = type->last_item; + while (item) { + if ((~item->field_0x00 & 0xff) == 0) { + return; // identifier == 0xff + } + item->field_0x00 += 1; + if ((~item->field_0x00 & 0xff) == 0) { + // identifier == 0xff + cache->table2().remove_ptr((const NodeCache::ItemKey *)item); + data::ptr node = item->node; + node->destroy_self(*subgraph->_graph); + node->destroy_value(*subgraph->_graph); + subgraph->_graph->remove_all_inputs(node); + } else { + subgraph->_other_state |= CacheState::Option1; + } + item = item->prev; + } + }, + &context); + } +} + +#pragma mark - Encoding + +void Subgraph::encode(Encoder &encoder) const { + auto zone_id = info().zone_id(); + if (zone_id != 0) { + encoder.encode_varint(8); + encoder.encode_varint(zone_id); + } + + if (_context_id != 0) { + encoder.encode_varint(0x10); + encoder.encode_varint(_context_id); + } + + for (auto parent : _parents) { + auto zone_id = parent->info().zone_id(); + if (zone_id != 0) { + encoder.encode_varint(0x18); + encoder.encode_varint(zone_id); + } + } + + for (auto child : _children) { + auto zone_id = child.subgraph()->info().zone_id(); + if (zone_id != 0) { + encoder.encode_varint(0x20); + encoder.encode_varint(zone_id); + } + } + + if (!is_valid()) { + encoder.encode_varint(0x28); + encoder.encode_varint(1); + } + + for (auto page : pages()) { + for (uint32_t iteration = 0; iteration < 2; ++iteration) { + AttributeIDList list = + iteration == 0 ? (AttributeIDList)AttributeIDList1(page) : (AttributeIDList)AttributeIDList2(page); + for (auto attribute : list) { + encoder.encode_varint(0x32); + encoder.begin_length_delimited(); + + if (attribute == 0) { + encoder.encode_varint(0x12); + encoder.begin_length_delimited(); + _graph->encode_node(encoder, attribute.to_node(), false); // TODO: check can handle null attribute + encoder.end_length_delimited(); + } else { + encoder.encode_varint(8); + encoder.encode_varint(attribute); + + if (attribute.is_direct()) { + encoder.encode_varint(0x12); + encoder.begin_length_delimited(); + _graph->encode_node(encoder, attribute.to_node(), false); + encoder.end_length_delimited(); + } else if (attribute.is_indirect()) { + encoder.encode_varint(0x1a); + encoder.begin_length_delimited(); + _graph->encode_indirect_node(encoder, attribute.to_indirect_node()); + encoder.end_length_delimited(); + } + } + + encoder.end_length_delimited(); + } + } + } + + if (_tree_root) { + encoder.encode_varint(0x3a); + encoder.begin_length_delimited(); + _graph->encode_tree(encoder, _tree_root); + encoder.end_length_delimited(); + } +} + +#pragma mark - Printing + +void Subgraph::print(uint32_t indent_level) { + uint64_t indent_length = 2 * indent_level; + char *indent_string = (char *)alloca(indent_length + 1); + memset(indent_string, ' ', indent_length); + indent_string[indent_length] = '\0'; + + fprintf(stdout, "%s+ %p: %u in %lu [", indent_string, this, info().zone_id(), (unsigned long)_context_id); + + bool first = true; + for (auto page : pages()) { + for (auto attribute : AttributeIDList1(page)) { + if (!attribute.is_direct()) { + continue; + } + fprintf(stdout, "%s%u", first ? "" : " ", AGAttribute(attribute)); + if (attribute.to_node().subgraph_flags()) { + fprintf(stdout, "(%u)", attribute.to_node().subgraph_flags()); + } + first = false; + } + } + + fwrite("]\n", 2, 1, stdout); + + for (auto child : _children) { + child.subgraph()->print(indent_level + 1); + } +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Subgraph/Subgraph.h b/Sources/ComputeCxx/Subgraph/Subgraph.h index 34f2535..5de28e8 100644 --- a/Sources/ComputeCxx/Subgraph/Subgraph.h +++ b/Sources/ComputeCxx/Subgraph/Subgraph.h @@ -1,21 +1,220 @@ #pragma once #include +#include +#include "Attribute/AttributeID.h" +#include "Attribute/Node/Node.h" +#include "Closure/ClosureFunction.h" +#include "Data/Pointer.h" #include "Data/Zone.h" +#include "Graph/AGGraph.h" +#include "Graph/Graph.h" +#include "Private/CFRuntime.h" +#include "Vector/IndirectPointerVector.h" CF_ASSUME_NONNULL_BEGIN +struct AGSubgraphStorage; + namespace AG { +namespace swift { +class metadata; +} class Graph; +class Node; +class Encoder; + +class SubgraphObject { + private: + CFRuntimeBase _base; + Subgraph *_subgraph; + + public: + Subgraph *subgraph() { return _subgraph; }; + void clear_subgraph() { _subgraph = nullptr; }; +}; class Subgraph : public data::zone { + public: + class NodeCache; + class SubgraphChild { + private: + uintptr_t _data; + + public: + enum Flags : uint32_t {}; + SubgraphChild(Subgraph *subgraph, Flags flags) { _data = (uintptr_t)subgraph | (flags & 0x3); }; + Subgraph *subgraph() const { return reinterpret_cast(_data & ~0x3); }; + Flags flags() const { return (Flags)(_data & 0x3); }; + bool operator==(const Subgraph *other) const { return subgraph() == other; }; + }; + + struct Flags { + uint8_t value1; + uint8_t value2; + uint8_t value3; + uint8_t value4; + bool is_null() const { return value1 == 0 && value2 == 0 and value3 == 0 && value4 == 0; }; + }; + + enum CacheState : uint8_t { + Option1 = 1 << 0, // added to graph._subgraphs_with_cached_nodes, or needs collect? + Option2 = 1 << 1, // Is calling cache collect + }; + + enum ValidationState : uint8_t { + Valid = 0, + InvalidationScheduled = 1, + Invalidated = 2, + GraphDestroyed = 3, + }; + private: - Graph *_graph; + static pthread_key_t _current_subgraph_key; + + SubgraphObject *_object; + Graph *_Nullable _graph; + uint64_t _context_id; + + indirect_pointer_vector _parents; + vector _children; + + struct Observer { + ClosureFunctionVV callback; + uint64_t observer_id; + }; + data::ptr *> _observers; + uint32_t _traversal_seed; + + uint32_t _index; + + data::ptr _cache; + data::ptr _tree_root; + + Flags _flags; + ValidationState _validation_state; + uint8_t _other_state; public: - Graph &graph() const { return *_graph; }; + static void make_current_subgraph_key(); + static Subgraph *_Nullable current_subgraph(); + static void set_current_subgraph(Subgraph *_Nullable subgraph); + + Subgraph(SubgraphObject *object, Graph::Context &context, AttributeID attribute); + ~Subgraph(); + + uint32_t subgraph_id() const { return info().zone_id(); }; + + // MARK: CoreFoundation + + static Subgraph *from_cf(AGSubgraphStorage *storage); + AGSubgraphStorage *to_cf() const; + void clear_object(); + + // MARK: Graph + + Graph *_Nullable graph() const { return _graph; }; + uint64_t context_id() const { return _context_id; }; + + bool is_valid() const { return _validation_state == ValidationState::Valid; }; + ValidationState validation_state() { return _validation_state; }; + + uint8_t other_state() { return _other_state; }; + void set_other_state(uint8_t other_state) { _other_state = other_state; }; + + void invalidate_and_delete_(bool delete_subgraph); + void invalidate_now(Graph &graph); + void graph_destroyed(); + + // MARK: Managing children + + void add_child(Subgraph &child, SubgraphChild::Flags flags); + void remove_child(Subgraph &child, bool flag); + vector &children() { return _children; }; + + bool ancestor_of(const Subgraph &other); + + template + requires std::invocable && std::same_as, bool> + void foreach_ancestor(Callable body); + + indirect_pointer_vector parents() { return _parents; }; + + // MARK: Attributes + + void add_node(data::ptr node); + void add_indirect(data::ptr node, bool flag); + + void insert_attribute(AttributeID attribute, bool after_flagged_nodes); + + void unlink_attribute(AttributeID attribute); + + void update(uint8_t flags); + + // MARK: Traversal + + static std::atomic _last_traversal_seed; + + void apply(Flags flags, ClosureFunctionAV body); + + // MARK: Tree + + data::ptr tree_root() { return _tree_root; }; + + void begin_tree(AttributeID value, const swift::metadata *_Nullable type, + uint32_t flags); // TODO: check can be null from Subgraph() + void end_tree(); + + void set_tree_owner(AttributeID attribute); + void add_tree_value(AttributeID attribute, const swift::metadata *type, const char *key, uint32_t flags); + + AttributeID tree_node_at_index(data::ptr tree_element, uint64_t index); + data::ptr tree_subgraph_child(data::ptr tree_element); + + // MARK: Managing flags + + // flags 2 and 4 control propogation + // flags 1 and 3 are the values themselvs + // flags 3 and 4 are the dirty subset of 1 and 2 + + void set_flags(data::ptr node, AGAttributeFlags flags); + + void add_flags(AGAttributeFlags flags); + void add_dirty_flags(AGAttributeFlags dirty_flags); + + void propagate_flags(); + void propagate_dirty_flags(); + + bool is_dirty(uint8_t mask) const; + bool intersects(uint8_t mask) const; + + // MARK: Managing observers + + uint64_t add_observer(ClosureFunctionVV &&callback); + void remove_observer(uint64_t observer_id); + void notify_observers(); + + // MARK: Index + + uint32_t index() const { return _index; }; + void set_index(uint32_t index) { _index = index; }; + + // MARK: Cache + + data::ptr cache_fetch(uint64_t identifier, const swift::metadata &type, void *body, + ClosureFunctionCI closure); + void cache_insert(data::ptr node); + void cache_collect(); + + // MARK: Encoding + + void encode(Encoder &encoder) const; + + // MARK: Printing + + void print(uint32_t indent_level); }; } // namespace AG diff --git a/Sources/ComputeCxx/Swift/AGTuple.cpp b/Sources/ComputeCxx/Swift/AGTuple.cpp index 76ba96d..f8b0214 100644 --- a/Sources/ComputeCxx/Swift/AGTuple.cpp +++ b/Sources/ComputeCxx/Swift/AGTuple.cpp @@ -194,9 +194,8 @@ void AGTupleWithBuffer(AGTupleType tuple_type, size_t count, const void *context) { auto metadata = reinterpret_cast(tuple_type); auto buffer_size = metadata->vw_stride() * count; - void *buffer; if (buffer_size <= 0x1000) { - buffer = (unsigned char *)alloca(buffer_size); + void *buffer = (unsigned char *)alloca(buffer_size); bzero((void *)buffer, buffer_size); AGUnsafeMutableTuple tuple = {tuple_type, buffer}; function(context, tuple); diff --git a/Sources/ComputeCxx/Swift/SwiftShims.h b/Sources/ComputeCxx/Swift/SwiftShims.h index 5e86fee..ee3d494 100644 --- a/Sources/ComputeCxx/Swift/SwiftShims.h +++ b/Sources/ComputeCxx/Swift/SwiftShims.h @@ -15,6 +15,9 @@ AG_SWIFT_CC(swift) bool AGDispatchEquatable(const void *lhs_value, const void *rhs_value, const ::swift::Metadata *type, const ::swift::equatable_support::EquatableWitnessTable *wt); +AG_SWIFT_CC(swift) +bool AGSetTypeForKey(CFMutableDictionaryRef dict, CFStringRef key, const ::swift::Metadata *type); + CF_EXTERN_C_END CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Trace/Trace.cpp b/Sources/ComputeCxx/Trace/Trace.cpp new file mode 100644 index 0000000..69dc13f --- /dev/null +++ b/Sources/ComputeCxx/Trace/Trace.cpp @@ -0,0 +1,12 @@ +#include "Trace.h" + +namespace AG { + +void Trace::log_message(const char *format, ...) { + va_list args; + va_start(args, format); + log_message_v(format, args); + va_end(args); +} + +} // namespace AG diff --git a/Sources/ComputeCxx/Trace/Trace.h b/Sources/ComputeCxx/Trace/Trace.h new file mode 100644 index 0000000..46d2ba2 --- /dev/null +++ b/Sources/ComputeCxx/Trace/Trace.h @@ -0,0 +1,99 @@ +#pragma once + +#include + +#include "Graph/Graph.h" +#include "UniqueID/AGUniqueID.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +class Node; +class Subgraph; + +class Trace { + protected: + uint64_t _trace_id; + + public: + uint64_t trace_id() { return _trace_id; } + + Trace() : _trace_id(AGMakeUniqueID()){}; + virtual ~Trace(){}; + + // Trace + virtual void begin_trace(const Graph &graph){}; + virtual void end_trace(const Graph &graph){}; + virtual void sync_trace(){}; + + // Log + virtual void log_message_v(const char *format, va_list args){}; + void log_message(const char *format, ...); + + // Updates + virtual void begin_update(const Subgraph &subgraph, uint32_t options){}; + virtual void end_update(const Subgraph &subgraph){}; + virtual void begin_update(const Graph::UpdateStack &update_stack, data::ptr node, uint32_t options){}; + virtual void end_update(const Graph::UpdateStack &update_stack, data::ptr node, + Graph::UpdateStatus update_status){}; + virtual void begin_update(data::ptr node){}; + virtual void end_update(data::ptr node, bool changed){}; + virtual void begin_update(const Graph::Context &context){}; + virtual void end_update(const Graph::Context &context){}; + + virtual void begin_invalidation(const Graph::Context &context, AttributeID attribute){}; + virtual void end_invalidation(const Graph::Context &context, AttributeID attribute){}; + + virtual void begin_modify(data::ptr node){}; + virtual void end_modify(data::ptr node){}; + + virtual void begin_event(data::ptr node, uint32_t event_id){}; + virtual void end_event(data::ptr node, uint32_t event_id){}; + + virtual void created(const Graph::Context &context){}; + virtual void destroy(const Graph::Context &context){}; + virtual void needs_update(const Graph::Context &context){}; + + virtual void created(const Subgraph &subgraph){}; + virtual void invalidate(const Subgraph &subgraph){}; + virtual void destroy(const Subgraph &subgraph){}; + + virtual void add_child(const Subgraph &subgraph, const Subgraph &child){}; + virtual void remove_child(const Subgraph &subgraph, const Subgraph &child){}; + + virtual void added(data::ptr node){}; + + virtual void add_edge(data::ptr node, AttributeID input, uint8_t input_edge_flags){}; + virtual void remove_edge(data::ptr node, uint32_t input_index){}; + virtual void set_edge_pending(data::ptr node, uint32_t input_index, bool pending){}; + + virtual void set_dirty(data::ptr node, bool dirty){}; + virtual void set_pending(data::ptr node, bool pending){}; + + virtual void set_value(data::ptr node, const void *value){}; + virtual void mark_value(data::ptr node){}; + + virtual void added(data::ptr indirect_node){}; + + virtual void set_source(data::ptr indirect_node, AttributeID source){}; + virtual void set_dependency(data::ptr indirect_node, AttributeID dependency){}; + + virtual void set_deadline(uint64_t deadline){}; + virtual void passed_deadline(){}; + + virtual void mark_profile(const Graph &graph, uint32_t options){}; + + virtual void custom_event(const Graph::Context &context, const char *event_name, const void *value, + const swift::metadata &type){}; + virtual void named_event(const Graph::Context &context, uint32_t event_id, uint32_t num_event_args, + const uint64_t *event_args, CFDataRef data, uint32_t arg6){}; + virtual bool named_event_enabled(uint32_t event_id) { return false; }; + + virtual void compare_failed(data::ptr node, const void *lhs, const void *rhs, size_t range_offset, + size_t range_size, const swift::metadata *_Nullable type){}; +}; + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/UniqueID/AGUniqueID.cpp b/Sources/ComputeCxx/UniqueID/AGUniqueID.cpp new file mode 100644 index 0000000..6b1747a --- /dev/null +++ b/Sources/ComputeCxx/UniqueID/AGUniqueID.cpp @@ -0,0 +1,6 @@ +#include "AGUniqueID.h" + +uint64_t AGMakeUniqueID() { + static uint64_t counter = 0; + return ++counter; +} diff --git a/Sources/ComputeCxx/UniqueID/AGUniqueID.h b/Sources/ComputeCxx/UniqueID/AGUniqueID.h new file mode 100644 index 0000000..2877cdc --- /dev/null +++ b/Sources/ComputeCxx/UniqueID/AGUniqueID.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +CF_ASSUME_NONNULL_BEGIN + +CF_EXTERN_C_BEGIN + +uint64_t AGMakeUniqueID() CF_SWIFT_NAME(makeUniqueID()); + +CF_EXTERN_C_END + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Vector/IndirectPointerVector.h b/Sources/ComputeCxx/Vector/IndirectPointerVector.h new file mode 100644 index 0000000..7cfbb6e --- /dev/null +++ b/Sources/ComputeCxx/Vector/IndirectPointerVector.h @@ -0,0 +1,233 @@ +#pragma once + +#include "CoreFoundation/CFBase.h" +#include +#include + +#include "Vector.h" + +CF_ASSUME_NONNULL_BEGIN + +namespace AG { + +/// A vector that efficiently stores a single element as a pointer or stores multiple elements as a vector. +template + requires std::unsigned_integral<_size_type> +class indirect_pointer_vector { + public: + using value_type = T *_Nonnull; + using reference = value_type &; + using const_reference = const value_type &; + using iterator = value_type *_Nonnull; + using const_iterator = const value_type *_Nonnull; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using size_type = _size_type; + + private: + uintptr_t _data; + enum { + TagMask = 0x1, + NullElement = 0x2, + }; + + using vector_type = vector; + + bool has_vector() const { return (_data & TagMask) == 1; }; + vector_type &get_vector() { + assert(has_vector()); + return *reinterpret_cast(_data & ~TagMask); + }; + const vector_type &get_vector() const { + assert(has_vector()); + return *reinterpret_cast(_data & ~TagMask); + }; + + public: + indirect_pointer_vector() = default; + ~indirect_pointer_vector(); + + // Element access + + reference operator[](size_type pos) { + assert(pos < size()); + if (has_vector()) { + return get_vector()[pos]; + } else { + return reinterpret_cast(_data); + } + }; + const_reference operator[](size_type pos) const { + assert(pos < size()); + if (has_vector()) { + return get_vector()[pos]; + } else { + return *reinterpret_cast(_data); + } + }; + + reference front() { return has_vector() ? get_vector().front() : reinterpret_cast(_data); }; + const_reference front() const { + if (has_vector()) { + return get_vector().front(); + } else { + return *reinterpret_cast(_data); + } + }; + + // Iterators + + iterator begin() { return has_vector() ? get_vector().begin() : reinterpret_cast(&_data); }; + iterator end() { + if (has_vector()) { + return get_vector().end(); + } else { + size_type size = _data == 0 ? 0 : 1; + return &reinterpret_cast(&_data)[size]; + } + }; + const_iterator cbegin() const { + return has_vector() ? get_vector().cbegin() : reinterpret_cast(&_data); + }; + const_iterator cend() const { + if (has_vector()) { + return get_vector().cend(); + } else { + size_type size = _data == 0 ? 0 : 1; + return &reinterpret_cast(&_data)[size]; + } + }; + const_iterator begin() const { return cbegin(); }; + const_iterator end() const { return cend(); }; + + reverse_iterator rbegin() { return std::reverse_iterator(end()); }; + reverse_iterator rend() { return std::reverse_iterator(begin()); }; + const_reverse_iterator crbegin() const { return std::reverse_iterator(cend()); }; + const_reverse_iterator crend() const { return std::reverse_iterator(cbegin()); }; + const_reverse_iterator rbegin() const { return crbegin(); }; + const_reverse_iterator rend() const { return crend(); }; + + // Capacity + + bool empty() const { return has_vector() ? get_vector().empty() : _data == 0; }; + size_type size() const { return has_vector() ? get_vector().size() : _data == 0 ? 0 : 1; }; + + // Modifiers + + void clear(); + + iterator erase(iterator pos); + iterator erase(iterator first, iterator last); + + void push_back(const value_type &value); + void push_back(value_type &&value); + + void resize(size_type count); +}; + +template + requires std::unsigned_integral +indirect_pointer_vector::~indirect_pointer_vector() { + clear(); +}; + +template + requires std::unsigned_integral +void indirect_pointer_vector::clear() { + if (has_vector()) { + vector_type *vector_pointer = reinterpret_cast(_data & ~TagMask); + if (vector_pointer != 0) { + delete vector_pointer; + } + } + _data = 0; +} + +template + requires std::unsigned_integral +indirect_pointer_vector::iterator indirect_pointer_vector::erase(iterator pos) { + if (pos == end()) { + return end(); + } + return erase(pos, pos + 1); +} + +template + requires std::unsigned_integral +indirect_pointer_vector::iterator indirect_pointer_vector::erase(iterator first, + iterator last) { + auto count = last - first; + if (count == 0) { + return last; + } + if (has_vector()) { + return get_vector().erase(first, last); + } else { + assert(count <= 1); + if (count == 1 && first == begin()) { + _data = 0; + } + return end(); + } +} + +template + requires std::unsigned_integral +void indirect_pointer_vector::push_back(const value_type &value) { + if (has_vector()) { + get_vector()->push_back(value); + } else { + if (_data == 0) { + _data = value; + } else { + vector_type *vector = new vector_type(); + vector->push_back(_data); + vector->push_back(value); + _data = vector | 1; + } + } +} + +template + requires std::unsigned_integral +void indirect_pointer_vector::push_back(value_type &&value) { + if (has_vector()) { + get_vector().push_back(value); + } else { + if (_data == 0) { + _data = reinterpret_cast(std::move(value)); + } else { + vector_type *vector = new vector_type(); + vector->push_back(reinterpret_cast(std::move(_data))); + vector->push_back(value); + _data = (uintptr_t)vector | 1; + } + } +} + +template + requires std::unsigned_integral +void indirect_pointer_vector::resize(size_type count) { + if (has_vector()) { + get_vector().resize(count); + return; + } + if (count == 1) { + if (_data == 0) { + _data = NullElement; // How does this affect size,empty and capacity? + } + return; + } + if (count == 0) { + _data = 0; + return; + } + // put single element into vector + vector_type *vector = new vector_type(); + vector->push_back(reinterpret_cast(std::move(_data))); + _data = vector | 1; +} + +} // namespace AG + +CF_ASSUME_NONNULL_END diff --git a/Sources/ComputeCxx/Vector/Vector.h b/Sources/ComputeCxx/Vector/Vector.h index 01a0616..717ffb3 100644 --- a/Sources/ComputeCxx/Vector/Vector.h +++ b/Sources/ComputeCxx/Vector/Vector.h @@ -1,9 +1,15 @@ #pragma once #include +#include +#include #include #include +#include #include +#include + +#include "Errors/Errors.h" CF_ASSUME_NONNULL_BEGIN @@ -35,12 +41,12 @@ class vector { ~vector(); // Non-copyable - + vector(const vector &) = delete; vector &operator=(const vector &) = delete; // Move - + vector(vector &&); vector &operator=(vector &&); @@ -102,7 +108,197 @@ class vector { static_assert(std::contiguous_iterator::iterator>); static_assert(std::contiguous_iterator::const_iterator>); - +namespace details { + +template + requires std::unsigned_integral +void *_Nullable realloc_vector(void *_Nullable buffer, void *_inline_buffer, size_type inline_capacity, + size_type *_Nonnull size, size_type preferred_new_size) { + // copy data from heap buffer into inline buffer if possible + if (preferred_new_size <= inline_capacity) { + if (buffer) { + memcpy(_inline_buffer, buffer, preferred_new_size * element_size_bytes); + free(buffer); + *size = inline_capacity; + } + return nullptr; + } + + size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size_bytes); + size_type new_size = (size_type)(new_size_bytes / element_size_bytes); + if (new_size == *size) { + // nothing to do + return buffer; + } + + void *new_buffer = realloc(buffer, new_size_bytes); + if (!new_buffer) { + precondition_failure("allocation failure"); + } + + // copy data from inline buffer into heap buffer + if (!buffer) { + memcpy(new_buffer, _inline_buffer, (*size) * element_size_bytes); + } + + *size = new_size; + return new_buffer; +} + +} // namespace details + +template + requires std::unsigned_integral +vector::~vector() { + for (auto i = 0; i < _size; i++) { + data()[i].~T(); + } + if (_buffer) { + free((void *)_buffer); + } +} + +template + requires std::unsigned_integral +void vector::reserve_slow(size_type new_cap) { + size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + _buffer = reinterpret_cast(details::realloc_vector( + (void *)_buffer, (void *)_inline_buffer, _inline_capacity, &_capacity, effective_new_cap)); +} + +template + requires std::unsigned_integral +void vector::reserve(size_type new_cap) { + if (new_cap <= capacity()) { + return; + } + reserve_slow(new_cap); +} + +template + requires std::unsigned_integral +void vector::shrink_to_fit() { + if (capacity() > _size) { + _buffer = reinterpret_cast(details::realloc_vector( + _buffer, _inline_buffer, _inline_capacity, &_capacity, _size)); + } +} + +template + requires std::unsigned_integral +void vector::clear() { + for (auto i = 0; i < _size; i++) { + data()[i].~T(); + } + _size = 0; +} + +template + requires std::unsigned_integral +vector::iterator vector::insert(const_iterator pos, + const T &value) { + reserve(_size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (mutable_pos) value_type(value); + _size += 1; + return end(); +} + +template + requires std::unsigned_integral +vector::iterator vector::insert(const_iterator pos, + T &&value) { + reserve(_size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (pos) value_type(std::move(value)); + _size += 1; + return end(); +} + +template + requires std::unsigned_integral +vector::iterator vector::erase(iterator pos) { + if (pos == end()) { + return end(); + } + return erase(pos, pos + 1); +} + +template + requires std::unsigned_integral +vector::iterator vector::erase(iterator first, + iterator last) { + auto count = last - first; + if (count == 0) { + return last; + } + for (auto iter = first; iter != last; iter++) { + iter->~T(); + } + for (auto iter = last, old_end = end(); iter != old_end; iter++) { + std::swap(*(iter - count), *iter); + } + _size -= count; + return end(); +} + +template + requires std::unsigned_integral +void vector::push_back(const T &value) { + reserve(_size + 1); + new (&data()[_size]) value_type(value); + _size += 1; +} + +template + requires std::unsigned_integral +void vector::push_back(T &&value) { + reserve(_size + 1); + new (&data()[_size]) value_type(std::move(value)); + _size += 1; +} + +template + requires std::unsigned_integral +void vector::pop_back() { + assert(size() > 0); + data()[_size - 1].~T(); + _size -= 1; +} + +template + requires std::unsigned_integral +void vector::resize(size_type count) { + reserve(count); + if (count < _size) { + for (auto i = count; i < _size; i++) { + data()[i].~T(); + } + } else if (count > _size) { + for (auto i = _size; i < count; i++) { + new (&data()[i]) value_type(); + } + } + _size = count; +} + +template + requires std::unsigned_integral +void vector::resize(size_type count, const value_type &value) { + reserve(count); + if (count < _size) { + for (auto i = count; i < _size; i++) { + data()[i].~T(); + } + } else if (count > _size) { + for (auto i = _size; i < count; i++) { + new (&data()[i]) value_type(value); + } + } + _size = count; +} // MARK: Specialization for empty stack buffer @@ -131,12 +327,12 @@ class vector { ~vector(); // Non-copyable - + vector(const vector &) = delete; vector &operator=(const vector &) = delete; // Move - + vector(vector &&other) noexcept; vector &operator=(vector &&other) noexcept; @@ -195,6 +391,211 @@ class vector { void resize(size_type count, const value_type &value); }; +namespace details { + +template + requires std::unsigned_integral +void *_Nullable realloc_vector(void *buffer, size_type *_Nonnull size, size_type preferred_new_size) { + if (preferred_new_size == 0) { + *size = 0; + free(buffer); + return nullptr; + } + + size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size); + size_type new_size = (size_type)(new_size_bytes / element_size); + if (new_size == *size) { + // nothing to do + return buffer; + } + + void *new_buffer = realloc(buffer, new_size_bytes); + if (!new_buffer) { + precondition_failure("allocation failure"); + } + *size = new_size; + return new_buffer; +} + +} // namespace details + +template + requires std::unsigned_integral +vector::~vector() { + for (auto i = 0; i < _size; i++) { + _buffer[i].~T(); + } + if (_buffer) { + free(_buffer); + } +} + +template + requires std::unsigned_integral +vector::vector(vector &&other) noexcept + : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { + other._size = 0; + other._capacity = 0; +} + +template + requires std::unsigned_integral +vector &vector::operator=(vector &&other) noexcept { + if (this != &other) { + for (auto i = 0; i < _size; i++) { + _buffer[i].~T(); + } + if (_buffer) { + free(_buffer); + } + _buffer = other._buffer; + _size = other._size; + _capacity = other._capacity; + other._buffer = nullptr; + other._size = 0; + other._capacity = 0; + } + return *this; +} + +template + requires std::unsigned_integral +void vector::reserve_slow(size_type new_cap) { + size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + _buffer = + reinterpret_cast(details::realloc_vector(_buffer, &_capacity, effective_new_cap)); +} + +template + requires std::unsigned_integral +void vector::reserve(size_type new_cap) { + if (new_cap <= capacity()) { + return; + } + reserve_slow(new_cap); +} + +template + requires std::unsigned_integral +void vector::shrink_to_fit() { + if (capacity() > size()) { + _buffer = reinterpret_cast(details::realloc_vector(_buffer, &_capacity, 0)); + } +} + +template + requires std::unsigned_integral +void vector::clear() { + for (auto i = 0; i < _size; i++) { + data()[i].~T(); + } + _size = 0; +} + +template + requires std::unsigned_integral +vector::iterator vector::insert(const_iterator pos, const T &value) { + reserve(_size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (mutable_pos) value_type(value); + _size += 1; + return end(); +} + +template + requires std::unsigned_integral +vector::iterator vector::insert(const_iterator pos, T &&value) { + reserve(_size + 1); + iterator mutable_pos = begin() + (pos - begin()); + std::move_backward(mutable_pos, end(), end() + 1); + new (mutable_pos) value_type(std::move(value)); + _size += 1; + return end(); +} + +template + requires std::unsigned_integral +vector::iterator vector::erase(iterator pos) { + if (pos == end()) { + return end(); + } + return erase(pos, pos + 1); +} + +template + requires std::unsigned_integral +vector::iterator vector::erase(iterator first, iterator last) { + auto count = last - first; + if (count == 0) { + return last; + } + for (auto iter = first; iter != last; iter++) { + iter->~T(); + } + for (auto iter = last, old_end = end(); iter != old_end; iter++) { + std::swap(*(iter - count), *iter); + } + _size -= count; + return end(); +} + +template + requires std::unsigned_integral +void vector::push_back(const T &value) { + reserve(_size + 1); + new (&_buffer[_size]) value_type(value); + _size += 1; +} + +template + requires std::unsigned_integral +void vector::push_back(T &&value) { + reserve(_size + 1); + new (&_buffer[_size]) value_type(std::move(value)); + _size += 1; +} + +template + requires std::unsigned_integral +void vector::pop_back() { + assert(size() > 0); + data()[_size - 1].~T(); + _size -= 1; +} + +template + requires std::unsigned_integral +void vector::resize(size_type count) { + reserve(count); + if (count < _size) { + for (auto i = count; i < _size; i++) { + data()[i].~T(); + } + } else if (count > _size) { + for (auto i = _size; i < count; i++) { + new (&data()[i]) value_type(); + } + } + _size = count; +} + +template + requires std::unsigned_integral +void vector::resize(size_type count, const value_type &value) { + reserve(count); + if (count < _size) { + for (auto i = count; i < _size; i++) { + data()[i].~T(); + } + } else if (count > _size) { + for (auto i = _size; i < count; i++) { + new (&data()[i]) value_type(value); + } + } + _size = count; +} + // MARK: Specialization for unique_ptr template @@ -222,12 +623,12 @@ class vector, 0, _size_type> { ~vector(); // Non-copyable - + vector(const vector &) = delete; vector &operator=(const vector &) = delete; // Move - + vector(vector &&) noexcept; vector &operator=(vector &&) noexcept; @@ -286,8 +687,100 @@ class vector, 0, _size_type> { void resize(size_type count, const value_type &value); }; +template + requires std::unsigned_integral +vector, 0, size_type>::~vector() { + for (auto i = 0; i < _size; i++) { + _buffer[i].reset(); + } + if (_buffer) { + free(_buffer); + } +} + +template + requires std::unsigned_integral +vector, 0, size_type>::vector(vector &&other) noexcept + : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { + other._size = 0; + other._capacity = 0; +} + +template + requires std::unsigned_integral +vector, 0, size_type> & +vector, 0, size_type>::operator=(vector &&other) noexcept { + if (this != &other) { + for (auto i = 0; i < _size; i++) { + _buffer[i].~T(); + } + if (_buffer) { + free(_buffer); + } + _buffer = other._buffer; + _size = other._size; + _capacity = other._capacity; + other._buffer = nullptr; + other._size = 0; + other._capacity = 0; + } + return *this; +} + +template + requires std::unsigned_integral +void vector, 0, size_type>::reserve_slow(size_type new_cap) { + size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); + _buffer = reinterpret_cast *>( + details::realloc_vector)>(_buffer, &_capacity, + effective_new_cap)); +} + +template + requires std::unsigned_integral +void vector, 0, size_type>::reserve(size_type new_cap) { + if (new_cap <= capacity()) { + return; + } + reserve_slow(new_cap); +} + +template + requires std::unsigned_integral +vector, 0, size_type>::iterator +vector, 0, size_type>::erase(iterator pos) { + if (pos == end()) { + return end(); + } + return erase(pos, pos + 1); +} + +template + requires std::unsigned_integral +vector, 0, size_type>::iterator +vector, 0, size_type>::erase(iterator first, iterator last) { + auto count = last - first; + if (count == 0) { + return last; + } + for (auto iter = first; iter != last; iter++) { + iter->reset(); + } + for (auto iter = last, old_end = end(); iter != old_end; iter++) { + std::swap(*(iter - count), *iter); + } + _size -= count; + return end(); +} + +template + requires std::unsigned_integral +void vector, 0, size_type>::push_back(std::unique_ptr &&value) { + reserve(_size + 1); + new (&_buffer[_size]) value_type(std::move(value)); + _size += 1; +} + } // namespace AG CF_ASSUME_NONNULL_END - -#include "Vector.tpp" diff --git a/Sources/ComputeCxx/Vector/Vector.tpp b/Sources/ComputeCxx/Vector/Vector.tpp deleted file mode 100644 index 332dca5..0000000 --- a/Sources/ComputeCxx/Vector/Vector.tpp +++ /dev/null @@ -1,507 +0,0 @@ -#include "Vector.h" - -#include -#include -#include -#include - -#include "Errors/Errors.h" - -namespace AG { - -#pragma mark - Base implementation - -namespace details { - -template - requires std::unsigned_integral -void *_Nullable realloc_vector(void *_Nullable buffer, void *_Nonnull _inline_buffer, size_type inline_capacity, size_type *_Nonnull size, - size_type preferred_new_size) { - // copy data from heap buffer into inline buffer if possible - if (preferred_new_size <= inline_capacity) { - if (buffer) { - memcpy(_inline_buffer, buffer, preferred_new_size * element_size_bytes); - free(buffer); - *size = inline_capacity; - } - return nullptr; - } - - size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size_bytes); - size_type new_size = (size_type)(new_size_bytes / element_size_bytes); - if (new_size == *size) { - // nothing to do - return buffer; - } - - void *new_buffer = realloc(buffer, new_size_bytes); - if (!new_buffer) { - precondition_failure("allocation failure"); - } - - // copy data from inline buffer into heap buffer - if (!buffer) { - memcpy(new_buffer, _inline_buffer, (*size) * element_size_bytes); - } - - *size = new_size; - return new_buffer; -} - -} // namespace details - -template - requires std::unsigned_integral -vector::~vector() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } - if (_buffer) { - free((void *)_buffer); - } -} - -template - requires std::unsigned_integral -void vector::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = reinterpret_cast(details::realloc_vector( - (void *)_buffer, (void *)_inline_buffer, _inline_capacity, &_capacity, effective_new_cap)); -} - -template - requires std::unsigned_integral -void vector::reserve(size_type new_cap) { - if (new_cap <= capacity()) { - return; - } - reserve_slow(new_cap); -} - -template - requires std::unsigned_integral -void vector::shrink_to_fit() { - if (capacity() > _size) { - _buffer = reinterpret_cast( - details::realloc_vector(_buffer, _inline_buffer, _inline_capacity, &_capacity, _size)); - } -} - -template - requires std::unsigned_integral -void vector::clear() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } - _size = 0; -} - -template - requires std::unsigned_integral -vector::iterator vector::insert(const_iterator pos, - const T &value) { - reserve(_size + 1); - iterator mutable_pos = begin() + (pos - begin()); - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(value); - _size += 1; - return end(); -} - -template - requires std::unsigned_integral -vector::iterator vector::insert(const_iterator pos, T &&value) { - reserve(_size + 1); - iterator mutable_pos = begin() + (pos - begin()); - std::move_backward(mutable_pos, end(), end() + 1); - new (pos) value_type(std::move(value)); - _size += 1; - return end(); -} - -template - requires std::unsigned_integral -vector::iterator vector::erase(iterator pos) { - if (pos == end()) { - return end(); - } - return erase(pos, pos + 1); -} - -template - requires std::unsigned_integral -vector::iterator vector::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { - return last; - } - for (auto iter = first; iter != last; iter++) { - iter->~T(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; - return end(); -} - -template - requires std::unsigned_integral -void vector::push_back(const T &value) { - reserve(_size + 1); - new (&data()[_size]) value_type(value); - _size += 1; -} - -template - requires std::unsigned_integral -void vector::push_back(T &&value) { - reserve(_size + 1); - new (&data()[_size]) value_type(std::move(value)); - _size += 1; -} - -template - requires std::unsigned_integral -void vector::pop_back() { - assert(size() > 0); - data()[_size - 1].~T(); - _size -= 1; -} - -template - requires std::unsigned_integral -void vector::resize(size_type count) { - reserve(count); - if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } - } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(); - } - } - _size = count; -} - -template - requires std::unsigned_integral -void vector::resize(size_type count, const value_type &value) { - reserve(count); - if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } - } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(value); - } - } - _size = count; -} - -#pragma mark - Specialization for no inline buffer - -namespace details { - -template - requires std::unsigned_integral -void *_Nullable realloc_vector(void *_Nullable buffer, size_type *_Nonnull size, size_type preferred_new_size) { - if (preferred_new_size == 0) { - *size = 0; - free(buffer); - return nullptr; - } - - size_t new_size_bytes = malloc_good_size(preferred_new_size * element_size); - size_type new_size = (size_type)(new_size_bytes / element_size); - if (new_size == *size) { - // nothing to do - return buffer; - } - - void *new_buffer = realloc(buffer, new_size_bytes); - if (!new_buffer) { - precondition_failure("allocation failure"); - } - *size = new_size; - return new_buffer; -} - -} // namespace details - -template - requires std::unsigned_integral -vector::~vector() { - for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); - } - if (_buffer) { - free(_buffer); - } -} - -template - requires std::unsigned_integral -vector::vector(vector &&other) noexcept - : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { - other._size = 0; - other._capacity = 0; -} - -template - requires std::unsigned_integral -vector &vector::operator=(vector &&other) noexcept { - if (this != &other) { - for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); - } - if (_buffer) { - free(_buffer); - } - _buffer = other._buffer; - _size = other._size; - _capacity = other._capacity; - other._buffer = nullptr; - other._size = 0; - other._capacity = 0; - } - return *this; -} - -template - requires std::unsigned_integral -void vector::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = - reinterpret_cast(details::realloc_vector(_buffer, &_capacity, effective_new_cap)); -} - -template - requires std::unsigned_integral -void vector::reserve(size_type new_cap) { - if (new_cap <= capacity()) { - return; - } - reserve_slow(new_cap); -} - -template - requires std::unsigned_integral -void vector::shrink_to_fit() { - if (capacity() > size()) { - _buffer = reinterpret_cast(details::realloc_vector(_buffer, &_capacity, 0)); - } -} - -template - requires std::unsigned_integral -void vector::clear() { - for (auto i = 0; i < _size; i++) { - data()[i].~T(); - } - _size = 0; -} - -template - requires std::unsigned_integral -vector::iterator vector::insert(const_iterator pos, const T &value) { - reserve(_size + 1); - iterator mutable_pos = begin() + (pos - begin()); - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(value); - _size += 1; - return end(); -} - -template - requires std::unsigned_integral -vector::iterator vector::insert(const_iterator pos, T &&value) { - reserve(_size + 1); - iterator mutable_pos = begin() + (pos - begin()); - std::move_backward(mutable_pos, end(), end() + 1); - new (mutable_pos) value_type(std::move(value)); - _size += 1; - return end(); -} - -template - requires std::unsigned_integral -vector::iterator vector::erase(iterator pos) { - if (pos == end()) { - return end(); - } - return erase(pos, pos + 1); -} - -template - requires std::unsigned_integral -vector::iterator vector::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { - return last; - } - for (auto iter = first; iter != last; iter++) { - iter->~T(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; - return end(); -} - -template - requires std::unsigned_integral -void vector::push_back(const T &value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(value); - _size += 1; -} - -template - requires std::unsigned_integral -void vector::push_back(T &&value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(std::move(value)); - _size += 1; -} - -template - requires std::unsigned_integral -void vector::pop_back() { - assert(size() > 0); - data()[_size - 1].~T(); - _size -= 1; -} - -template - requires std::unsigned_integral -void vector::resize(size_type count) { - reserve(count); - if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } - } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(); - } - } - _size = count; -} - -template - requires std::unsigned_integral -void vector::resize(size_type count, const value_type &value) { - reserve(count); - if (count < _size) { - for (auto i = count; i < _size; i++) { - data()[i].~T(); - } - } else if (count > _size) { - for (auto i = _size; i < count; i++) { - new (&data()[i]) value_type(value); - } - } - _size = count; -} - -#pragma mark - Specialization for unique_ptr - -template - requires std::unsigned_integral -vector, 0, size_type>::~vector() { - for (auto i = 0; i < _size; i++) { - _buffer[i].reset(); - } - if (_buffer) { - free(_buffer); - } -} - -template - requires std::unsigned_integral -vector, 0, size_type>::vector(vector &&other) noexcept - : _buffer(std::exchange(other._buffer, nullptr)), _size(other._size), _capacity(other._capacity) { - other._size = 0; - other._capacity = 0; -} - -template - requires std::unsigned_integral -vector, 0, size_type> & -vector, 0, size_type>::operator=(vector &&other) noexcept { - if (this != &other) { - for (auto i = 0; i < _size; i++) { - _buffer[i].~T(); - } - if (_buffer) { - free(_buffer); - } - _buffer = other._buffer; - _size = other._size; - _capacity = other._capacity; - other._buffer = nullptr; - other._size = 0; - other._capacity = 0; - } - return *this; -} - -template - requires std::unsigned_integral -void vector, 0, size_type>::reserve_slow(size_type new_cap) { - size_type effective_new_cap = std::max(capacity() * 1.5, new_cap * 1.0); - _buffer = reinterpret_cast *>( - details::realloc_vector)>(_buffer, &_capacity, - effective_new_cap)); -} - -template - requires std::unsigned_integral -void vector, 0, size_type>::reserve(size_type new_cap) { - if (new_cap <= capacity()) { - return; - } - reserve_slow(new_cap); -} - -template - requires std::unsigned_integral -vector, 0, size_type>::iterator -vector, 0, size_type>::erase(iterator pos) { - if (pos == end()) { - return end(); - } - return erase(pos, pos + 1); -} - -template - requires std::unsigned_integral -vector, 0, size_type>::iterator -vector, 0, size_type>::erase(iterator first, iterator last) { - auto count = last - first; - if (count == 0) { - return last; - } - for (auto iter = first; iter != last; iter++) { - iter->reset(); - } - for (auto iter = last, old_end = end(); iter != old_end; iter++) { - std::swap(*(iter - count), *iter); - } - _size -= count; - return end(); -} - -template - requires std::unsigned_integral -void vector, 0, size_type>::push_back(std::unique_ptr &&value) { - reserve(_size + 1); - new (&_buffer[_size]) value_type(std::move(value)); - _size += 1; -} - -} // namespace AG diff --git a/Sources/ComputeCxx/Version/AGVersion.cpp b/Sources/ComputeCxx/Version/AGVersion.cpp new file mode 100644 index 0000000..e1f6dc5 --- /dev/null +++ b/Sources/ComputeCxx/Version/AGVersion.cpp @@ -0,0 +1,3 @@ +#include "AGVersion.h" + +const uint32_t AGVersion = 0x2001e; diff --git a/Sources/ComputeCxx/Version/AGVersion.h b/Sources/ComputeCxx/Version/AGVersion.h new file mode 100644 index 0000000..c01c911 --- /dev/null +++ b/Sources/ComputeCxx/Version/AGVersion.h @@ -0,0 +1,9 @@ +#include +#include + +CF_EXTERN_C_BEGIN + +CF_EXPORT +const uint32_t AGVersion; + +CF_EXTERN_C_END diff --git a/Sources/ComputeCxx/include/Compute.h b/Sources/ComputeCxx/include/Compute.h index af15432..a8ea8e8 100644 --- a/Sources/ComputeCxx/include/Compute.h +++ b/Sources/ComputeCxx/include/Compute.h @@ -1,6 +1,10 @@ #include "Attribute/AGAttribute.h" +#include "Closure/AGClosure.h" #include "Comparison/AGComparison.h" +#include "Graph/AGDescription.h" #include "Graph/AGGraph.h" +#include "Graph/Tree/AGTreeElement.h" #include "Subgraph/AGSubgraph.h" #include "Swift/AGTuple.h" #include "Swift/AGType.h" +#include "UniqueID/AGUniqueID.h" diff --git a/Sources/ComputeCxxSwiftSupport/ComputeCxxSwiftSupport.swift b/Sources/ComputeCxxSwiftSupport/ComputeCxxSwiftSupport.swift index c996e9b..8ec810f 100644 --- a/Sources/ComputeCxxSwiftSupport/ComputeCxxSwiftSupport.swift +++ b/Sources/ComputeCxxSwiftSupport/ComputeCxxSwiftSupport.swift @@ -5,3 +5,12 @@ public func Equatable_isEqual_indirect( ) -> Bool { return lhs.pointee == rhs.pointee } + +@_silgen_name("AGSetTypeForKey") +public func setTypeForKey( + _ dict: inout [String: Any], + _ key: String, + _ type: Any.Type +) { + dict[key] = type +} diff --git a/Sources/Utilities/include/Utilities/ObjCPointer.h b/Sources/Utilities/include/Utilities/ObjCPointer.h index 1884f26..ff0db3d 100644 --- a/Sources/Utilities/include/Utilities/ObjCPointer.h +++ b/Sources/Utilities/include/Utilities/ObjCPointer.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include CF_ASSUME_NONNULL_BEGIN diff --git a/Tests/ComputeCxxTests/Data/ZoneTests.mm b/Tests/ComputeCxxTests/Data/ZoneTests.mm new file mode 100644 index 0000000..22db81b --- /dev/null +++ b/Tests/ComputeCxxTests/Data/ZoneTests.mm @@ -0,0 +1,47 @@ +#import +#import + +#include "Data/Table.h" +#include "Data/Zone.h" + +@interface ZoneTests : XCTestCase +@end + +@implementation ZoneTests + +- (void)setUp { + [super setUp]; + + AG::data::table::ensure_shared(); +} + +// MARK: Zones + +- (void)testZoneLifecycle { + // auto zone = AG::data::zone(); + // XCTAssertEqual(zone.info().zone_id(), 1); + // + // for (auto page : zone.pages()) { + // XCTFail(@"New zone shouldn't contain any pages"); + // } + // + // zone.clear(); +} + +// MARK: Alloc bytes + +- (void)testAllocBytes { + + auto zone = AG::data::zone(); + + auto bytes = zone.alloc_bytes(0x10, 3); + XCTAssert(bytes != nullptr); +} + +- (void)testAllocBytesWithExistingPage { +} + +- (void)testAllocBytesWithExistingPageInsufficientCapacity { +} + +@end diff --git a/Tests/ComputeCxxTests/Vector/IndirectPointerVectorTests.mm b/Tests/ComputeCxxTests/Vector/IndirectPointerVectorTests.mm new file mode 100644 index 0000000..6e4ec1f --- /dev/null +++ b/Tests/ComputeCxxTests/Vector/IndirectPointerVectorTests.mm @@ -0,0 +1,83 @@ +#import +#import + +#include "Containers/IndirectPointerVector.h" +#include "Data/Table.h" + +@interface IndirectPointerVectorTests : XCTestCase +@end + +@implementation IndirectPointerVectorTests + +- (void)testInit { + auto vector = AG::indirect_pointer_vector(); + XCTAssertTrue(vector.empty()); + XCTAssertEqual(vector.size(), 0); + + XCTAssert(vector.begin() == vector.end()); + + for (auto element : vector) { + XCTFail(@"New vector should be empty"); + } +} + +- (void)testClearWhenEmpty { + auto vector = AG::indirect_pointer_vector(); + + vector.clear(); +} + +- (void)testOneElement { + uint64_t testElement = 1; + + auto vector = AG::indirect_pointer_vector(); + vector.push_back(&testElement); + + XCTAssertFalse(vector.empty()); + XCTAssertEqual(vector.size(), 1); + + NSInteger i = 0; + for (auto element : vector) { + if (i == 0) { + XCTAssertEqual(element, &testElement); + } else { + XCTFail(@"Vector contains too many elements"); + } + ++i; + } + + vector.erase(vector.begin()); + XCTAssertTrue(vector.empty()); + XCTAssertEqual(vector.size(), 0); +} + +- (void)testTwoElements { + uint64_t testElement1 = 1; + uint64_t testElement2 = 1; + + auto vector = AG::indirect_pointer_vector(); + vector.push_back(&testElement1); + vector.push_back(&testElement2); + + XCTAssertFalse(vector.empty()); + XCTAssertEqual(vector.size(), 2); + + NSInteger i = 0; + for (auto element : vector) { + if (i == 0) { + XCTAssertEqual(element, &testElement1); + } else if (i == 1) { + XCTAssertEqual(element, &testElement2); + } else { + XCTFail(@"Vector contains too many elements"); + } + ++i; + } + + vector.erase(vector.begin()); + vector.erase(vector.begin()); + XCTAssertTrue(vector.empty()); + XCTAssertEqual(vector.size(), 0); +} + +@end diff --git a/Tests/ComputeTests/Shared/Attribute/AnyAttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/AnyAttributeTests.swift new file mode 100644 index 0000000..f21398c --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/AnyAttributeTests.swift @@ -0,0 +1,91 @@ +import Testing + +@Suite +final class AnyAttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func constantValue() throws { + let attributeNil = AnyAttribute.nil + #expect(attributeNil.rawValue == 2) + } + + @Test + func description() throws { + let attribute = AnyAttribute(rawValue: 0) + #expect(attribute.description == "#0") + + let attributeNil = AnyAttribute.nil + #expect(attributeNil.description == "#2") + } + + @Test + func current() { + #expect(AnyAttribute.current == nil) + } + + @Test + func setFlags() throws { + let attribute = AnyAttribute(Attribute(value: 0)) + #expect(attribute.flags == []) + + // Test mask = [] + attribute.flags = [] + + attribute.setFlags([.active], mask: []) + #expect(attribute.flags == []) + + attribute.setFlags([.removable], mask: []) + #expect(attribute.flags == []) + + attribute.setFlags([.active, .invalidatable], mask: []) + #expect(attribute.flags == []) + + // Test mask + attribute.flags = [] + attribute.setFlags([.active], mask: [.active]) + #expect(attribute.flags == [.active]) + + attribute.setFlags([.removable], mask: [.removable]) + #expect(attribute.flags == [.active, .removable]) + + attribute.setFlags([.invalidatable], mask: [.active]) + #expect(attribute.flags == [.removable]) + + attribute.setFlags([.active, .invalidatable], mask: [.active, .removable, .invalidatable]) + #expect(attribute.flags == [.active, .invalidatable]) + } + + @Test + func visitBody() async { + let attribute = Attribute(value: "string value") + + struct Visitor: AttributeBodyVisitor { + let confirm: Confirmation + func visit(body: UnsafePointer) where Body: _AttributeBody { + if body.pointee is External { + confirm() + } + } + } + + await confirmation(expectedCount: 1) { confirm in + var visitor = Visitor(confirm: confirm) + attribute.visitBody(&visitor) + } + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/AttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/AttributeTests.swift new file mode 100644 index 0000000..dd69a6d --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/AttributeTests.swift @@ -0,0 +1,99 @@ +import Testing + +struct Triple { + var first: A + var second: B + var third: C +} + +extension Triple: Sendable where A: Sendable, B: Sendable, C: Sendable {} + +@Suite +final class AttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func initWithValue() { + let intAttribute = Attribute(value: 0) + #expect(intAttribute.value == 0) + } + + @Test + func hashableAndEquatable() { + let a = Attribute(identifier: .nil) + let b = Attribute(identifier: .nil) + #expect(a == b) + #expect(a.hashValue == b.hashValue) + } + + @Test + func propertyWrapper() { + @Attribute(value: 0) var value + #expect(value == 0) + value = 3 + #expect(value == 3) + #expect(_value.setValue(4) == true) + #expect(value == 4) + let newAttribute = $value + value = 5 + #expect(newAttribute.wrappedValue == 5) + } + + @Test(arguments: [ + Triple(first: 0, second: 1, third: 2), + Triple(first: 3, second: 4, third: 5), + ]) + func attributeWithSubscript(_ value: Triple) { + let attribute = Attribute(value: value) + let offsetValue = attribute[offset: { _ in + PointerOffset, Int>(byteOffset: 8) + }] + #expect(offsetValue.wrappedValue == value.second) + #expect(attribute.first.wrappedValue == value.first) + #expect(attribute[keyPath: \.third].wrappedValue == value.third) + } + + @Test + func value() { + let attribute = Attribute(value: 5) + #expect(attribute.hasValue == true) + #expect(attribute.changedValue(options: []) == (5, false)) + + #expect(attribute.setValue(3) == true) + #expect(attribute.changedValue(options: []) == (3, false)) // TODO: How to test for changed == true + + // Unknown effect and untested. + // Just add here for linkage test + attribute.updateValue() + attribute.prefetchValue() + attribute.invalidateValue() + } + + @Test + func mutateBodyAPI() { + let attribute = Attribute(value: 5) + attribute.mutateBody(as: External.self, invalidating: true) { _ in + + } + } + + @Test + func flagSetter() { + let attribute = Attribute(value: ()) + attribute.flags = .active + #expect(attribute.flags == .active) + } +} diff --git a/Tests/ComputeTests/Shared/Attribute/AttributeTypeTests.swift b/Tests/ComputeTests/Shared/Attribute/AttributeTypeTests.swift new file mode 100644 index 0000000..ca197fc --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/AttributeTypeTests.swift @@ -0,0 +1,77 @@ +import Foundation +import Testing + +private struct CustomStruct { + var property: Int +} + +private enum CustomEnum { + case first + case second(Int) + case third(String) +} + +private indirect enum CustomRecursiveEnum { + case none + case recursive(CustomRecursiveEnum) +} + +private struct CustomType { + var string: String? + var integer: Int? + var structProperty: CustomStruct? + var existential: (any Equatable)? + var function: (() -> Void)? + var enumProperty: CustomEnum? + var recursiveEnumProperty: CustomRecursiveEnum? +} + +@Suite +final class AttributeTypeTests { + + static let prefetchLayouts = + Int(ProcessInfo.processInfo.environment["AG_PREFETCH_LAYOUTS", default: "0"], radix: 10) != 0 + static let asyncLayouts = Int(ProcessInfo.processInfo.environment["AG_ASYNC_LAYOUTS", default: "1"], radix: 10) != 0 + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + // TODO: use setenv + @Test(.enabled(if: prefetchLayouts && !asyncLayouts)) + func layout() { + let attribute = Attribute( + value: CustomType( + string: nil, + integer: nil, + structProperty: nil, + existential: nil, + function: nil, + enumProperty: nil, + recursiveEnumProperty: nil + ) + ) + let attributeType = attribute.identifier.info.type.pointee + + #expect(attributeType.typeID == Metadata(External.self)) + #expect(attributeType.valueTypeID == Metadata(CustomType.self)) + #expect(attributeType.flags == AGAttributeTypeFlags(rawValue: 19)) + + #expect(attributeType.layout != nil) + if let layout = attributeType.layout { + #expect(String(cString: layout) == "\u{1}\u{fffd}\u{fffd}D\u{4}\u{2}") + } + + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/ExternalTests.swift b/Tests/ComputeTests/Shared/Attribute/ExternalTests.swift new file mode 100644 index 0000000..a444c00 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/ExternalTests.swift @@ -0,0 +1,29 @@ +import Testing + +@Suite +final class ExternalTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func example() throws { + let type = External.self + let externalInt = type.init() + #expect(externalInt.description == "Int") + #expect(type.comparisonMode == [._1, ._2]) + #expect(type.flags == []) + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/FocusTests.swift b/Tests/ComputeTests/Shared/Attribute/FocusTests.swift new file mode 100644 index 0000000..b6509c3 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/FocusTests.swift @@ -0,0 +1,36 @@ +import Testing + +struct Demo { + var a: Int + var b: Double +} + +@Suite +final class FocusTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func example() throws { + let root = Attribute(value: Demo(a: 0, b: 1.0)) + let type = Focus.self + let focus = type.init(root: root, keyPath: \.a) + let d = focus.description + #expect(d == "• Int") + #expect(focus.value == 0) + #expect(type.flags == []) + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/Indirect/IndirectAttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/Indirect/IndirectAttributeTests.swift new file mode 100644 index 0000000..c8f4840 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/Indirect/IndirectAttributeTests.swift @@ -0,0 +1,28 @@ +import Testing + +@Suite +final class IndirectAttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func basic() { + let source = Attribute(value: 0) + let indirect = IndirectAttribute(source: source) + #expect(indirect.identifier != source.identifier) + #expect(indirect.source.identifier == source.identifier) + #expect(indirect.dependency == .init(rawValue: 0)) + } +} diff --git a/Tests/ComputeTests/Shared/Attribute/Optional/AnyOptionalAttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/Optional/AnyOptionalAttributeTests.swift new file mode 100644 index 0000000..80a944c --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/Optional/AnyOptionalAttributeTests.swift @@ -0,0 +1,67 @@ +import Testing + +@Suite +final class AnyOptionalAttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func basicInit() { + let o1 = AnyOptionalAttribute() + #expect(o1.identifier == .nil) + + let attr = AnyAttribute(rawValue: 0x1) + let o2 = AnyOptionalAttribute(attr) + #expect(o2.identifier == attr) + + let o3 = AnyOptionalAttribute(nil) + #expect(o3.identifier == .nil) + } + + @Test + func attribute() { + let o1 = AnyOptionalAttribute() + #expect(o1.attribute == nil) + + let attr = AnyAttribute(rawValue: 0x1) + let o2 = AnyOptionalAttribute(attr) + #expect(o2.attribute != nil) + } + + @Test + func current() { + let currentNil = AnyOptionalAttribute.current + #expect(currentNil.identifier == .nil) + } + + @Test + func description() { + let o1 = AnyOptionalAttribute() + #expect(o1.description == "nil") + + let attr = AnyAttribute(rawValue: 0x1) + let o2 = AnyOptionalAttribute(attr) + #expect(o2.description == "#1") + } + + @Test("Test symbol link") + func other() { + let optional = OptionalAttribute() + let anyOptional = AnyOptionalAttribute(optional) + _ = anyOptional.unsafeCast(to: Int.self) + _ = anyOptional.map { _ in 0 } + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/Optional/OptionalAttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/Optional/OptionalAttributeTests.swift new file mode 100644 index 0000000..8005262 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/Optional/OptionalAttributeTests.swift @@ -0,0 +1,52 @@ +import Testing + +@Suite +final class OptionalAttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func basicInit() { + let ao1 = AnyOptionalAttribute() + let o1 = OptionalAttribute() + #expect(o1 == OptionalAttribute(base: ao1)) + + let attr = Attribute(identifier: .init(rawValue: 0x1)) + let ao2 = AnyOptionalAttribute(attr.identifier) + let o2 = OptionalAttribute(attr) + #expect(o2 == OptionalAttribute(base: ao2)) + + let o3 = OptionalAttribute(nil) + #expect(o3.base.identifier == .nil) + } + + @Test(.disabled("crash for invalid data offset")) + func initWithWeak() { + let attr = Attribute(value: 0) + let weakAttr = WeakAttribute(attr) + let _ = OptionalAttribute(weakAttr) + } + + @Test + func description() { + let o1 = OptionalAttribute() + #expect(o1.description == "nil") + + let attr = AnyAttribute(rawValue: 0x1) + let o2 = OptionalAttribute(Attribute(identifier: attr)) + #expect(o2.description == "#1") + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/PointerOffsetTests.swift b/Tests/ComputeTests/Shared/Attribute/PointerOffsetTests.swift new file mode 100644 index 0000000..cf1b57a --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/PointerOffsetTests.swift @@ -0,0 +1,175 @@ +import Testing + +struct Tuple { + var first: A + var second: B +} + +@Suite +struct PointerOffsetTests { + + @Test + func plainInitAndProperty() { + typealias Base = Tuple + var offset = PointerOffset(byteOffset: 8) + #expect(offset.byteOffset == 8) + offset.byteOffset = 0 + #expect(offset.byteOffset == 0) + } + + @Test + func emptyInit() { + #expect(PointerOffset().byteOffset == 0) + #expect(PointerOffset().byteOffset == 0) + #expect(PointerOffset().byteOffset == 0) + } + + @Test + func plusOperator() { + typealias Base = Tuple + typealias Base2 = Tuple + let offset1 = PointerOffset(byteOffset: 8) + let offset2 = PointerOffset(byteOffset: 8) + let result = offset1 + offset2 + #expect(result.byteOffset == 16) + #expect(type(of: result) == PointerOffset.self) + } + + @Test + func invalidScenePointer() { + typealias Base = Tuple + let invalidPointer = PointerOffset.invalidScenePointer() + let stride = MemoryLayout.stride + #expect(stride == 16) + + // if compatibilityTestEnabled { + // if #available(iOS 18, macOS 15, *) { + // #expect(invalidPointer == UnsafeMutablePointer(bitPattern: 0x1)) + // } else { + // #expect(invalidPointer == UnsafeMutablePointer(bitPattern: stride)) + // } + // } else { + // #if OPENGRAPH_RELEASE_2024 + // #expect(invalidPointer == UnsafeMutablePointer(bitPattern: 0x1)) + // #else + // + // #endif + // } + #expect(invalidPointer == UnsafeMutablePointer(bitPattern: 0x1)) + } + + @Test(.bug("https://github.com/OpenSwiftUIProject/OpenGraph/issues/70", id: 70, "Verify fix of #70")) + func ofAndOffset() { + struct Empty { + var value: Void + } + + func helper( + expectedByteOffset: Int, + _: Base.Type, + _: Member.Type, + _ body: (inout Base) -> PointerOffset + ) { + let pointerOffsetType = PointerOffset.self + let offset = pointerOffsetType.offset { invalid in + withUnsafeMutablePointer(to: &invalid) { pointer in + #expect(pointer == PointerOffset.invalidScenePointer()) + } + return body(&invalid) + } + #expect(offset.byteOffset == expectedByteOffset) + } + + helper(expectedByteOffset: 0, Tuple.self, Void.self) { _ in fatalError("Unreachable") } + helper(expectedByteOffset: 0, Tuple.self, Empty.self) { _ in fatalError("Unreachable") } + + typealias Base = Triple + helper(expectedByteOffset: 0, Base.self, Int.self) { invalid in + .of(&invalid.first) + } + helper(expectedByteOffset: 8, Base.self, Int.self) { invalid in + .of(&invalid.second) + } + helper(expectedByteOffset: 0, Base.self, Empty.self) { invalid in + .of(&invalid.third) + } + } + + @Test("Extension API between UnsafePointer/UnsafeMutablePointer and PointerOffset") + func unsafePointerAndUnsafeMutablePointerExtension() { + do { + var tuple = Tuple(first: 1, second: 2.0) + typealias Base = Tuple + let firstOffset = PointerOffset(byteOffset: 0) + let secondOffset = PointerOffset(byteOffset: 8) + withUnsafePointer(to: tuple) { pointer in + #expect(pointer[offset: firstOffset] == 1) + #expect(pointer[offset: secondOffset].isApproximatelyEqual(to: 2.0)) + } + withUnsafeMutablePointer(to: &tuple) { pointer in + #expect(pointer[offset: firstOffset] == 1) + #expect(pointer[offset: secondOffset].isApproximatelyEqual(to: 2.0)) + + pointer[offset: firstOffset] = 3 + pointer[offset: secondOffset] = 4.0 + + #expect(pointer[offset: firstOffset] == 3) + #expect(pointer[offset: secondOffset].isApproximatelyEqual(to: 4.0)) + } + withUnsafePointer(to: &tuple) { pointer in + #expect(pointer[offset: firstOffset] == 3) + #expect(pointer[offset: secondOffset].isApproximatelyEqual(to: 4.0)) + } + #if !(!canImport(Darwin) && !DEBUG) + withUnsafePointer(to: tuple) { pointer in + #expect(pointer[offset: firstOffset] == 3) + #expect(pointer[offset: secondOffset].isApproximatelyEqual(to: 4.0)) + } + #expect(tuple.first == 3) + #expect(tuple.second.isApproximatelyEqual(to: 4.0)) + #endif + } + + do { + var triple = Triple(first: 0, second: 1, third: 2) + typealias Base = Triple + + let firstOffset = PointerOffset.offset { .of(&$0.first) } + let secondOffset = PointerOffset.offset { .of(&$0.second) } + let thirdOffset = PointerOffset.offset { .of(&$0.third) } + + withUnsafePointer(to: triple) { pointer in + #expect((pointer + firstOffset).pointee == 0) + #expect((pointer + secondOffset).pointee == 1) + #expect((pointer + thirdOffset).pointee == 2) + } + withUnsafeMutablePointer(to: &triple) { pointer in + #expect((pointer + firstOffset).pointee == 0) + #expect((pointer + secondOffset).pointee == 1) + #expect((pointer + thirdOffset).pointee == 2) + + (pointer + firstOffset).pointee = 3 + (pointer + secondOffset).pointee = 4 + (pointer + thirdOffset).pointee = 5 + + #expect((pointer + firstOffset).pointee == 3) + #expect((pointer + secondOffset).pointee == 4) + #expect((pointer + thirdOffset).pointee == 5) + } + withUnsafePointer(to: &triple) { pointer in + #expect((pointer + firstOffset).pointee == 3) + #expect((pointer + secondOffset).pointee == 4) + #expect((pointer + thirdOffset).pointee == 5) + } + withUnsafePointer(to: triple) { pointer in + #expect((pointer + firstOffset).pointee == 3) + #expect((pointer + secondOffset).pointee == 4) + #expect((pointer + thirdOffset).pointee == 5) + } + #expect(triple.first == 3) + #expect(triple.second == 4) + #expect(triple.third == 5) + } + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/Rule/RuleTests.swift b/Tests/ComputeTests/Shared/Attribute/Rule/RuleTests.swift new file mode 100644 index 0000000..a48c1fa --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/Rule/RuleTests.swift @@ -0,0 +1,15 @@ +import Testing + +@Suite +struct RuleTests { + + @Test + func ruleInitialValue() throws { + struct A: Rule { + typealias Value = Int + var value: Int + } + #expect(A.initialValue == nil) + } + +} diff --git a/Tests/ComputeTests/Shared/Attribute/Weak/WeakAttributeTests.swift b/Tests/ComputeTests/Shared/Attribute/Weak/WeakAttributeTests.swift new file mode 100644 index 0000000..da32791 --- /dev/null +++ b/Tests/ComputeTests/Shared/Attribute/Weak/WeakAttributeTests.swift @@ -0,0 +1,28 @@ +import Testing + +@Suite +final class WeakAttributeTests { + + nonisolated(unsafe) private static let sharedGraph = Graph() + private var graph: Graph + private var subgraph: Subgraph + + init() { + graph = Graph(shared: Self.sharedGraph) + subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + } + + deinit { + Subgraph.current = nil + } + + @Test + func initTest() { + let _ = WeakAttribute() + let _ = WeakAttribute(nil) + let attr = Attribute(value: 0) + let _ = WeakAttribute(attr) + } + +} diff --git a/Tests/ComputeTests/Shared/Data/UniqueIDTests.swift b/Tests/ComputeTests/Shared/Data/UniqueIDTests.swift new file mode 100644 index 0000000..637cca8 --- /dev/null +++ b/Tests/ComputeTests/Shared/Data/UniqueIDTests.swift @@ -0,0 +1,11 @@ +import Testing + +struct UniqueIDTests { + + @Test + func uniqueID() throws { + let initialID = makeUniqueID() + #expect(makeUniqueID() == initialID + 1) + } + +} diff --git a/Tests/ComputeTests/Shared/Graph/GraphContextTests.swift b/Tests/ComputeTests/Shared/Graph/GraphContextTests.swift new file mode 100644 index 0000000..d8b1b1d --- /dev/null +++ b/Tests/ComputeTests/Shared/Graph/GraphContextTests.swift @@ -0,0 +1,18 @@ +import Testing + +@Suite +struct GraphContextTests { + + @Test + func currentGraphContext() { + let graph = Graph() + + let subgraph = Subgraph(graph: graph) + Subgraph.current = subgraph + + let currentGraphContext = Subgraph.currentGraphContext + #expect(currentGraphContext != nil) + #expect(currentGraphContext == graph.graphContext) + } + +} diff --git a/Tests/ComputeTests/Shared/Graph/GraphTests.swift b/Tests/ComputeTests/Shared/Graph/GraphTests.swift new file mode 100644 index 0000000..a62e338 --- /dev/null +++ b/Tests/ComputeTests/Shared/Graph/GraphTests.swift @@ -0,0 +1,83 @@ +import Foundation +import Testing + +@Suite +struct GraphTests { + + @Test + func graphCreate() throws { + _ = Graph() + } + + @Test + func graphCreateShared() throws { + let graph = Graph() + _ = Graph(shared: graph) + _ = Graph(shared: nil) + } + + @Test + func graphArchiveJSON() throws { + struct Graphs: Codable { + var version: Int + var graphs: [Graph] + struct Graph: Codable {} + } + let name = "empty_graph.json" + Graph.archiveJSON(name: name) + let url = + if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) { + URL(filePath: NSTemporaryDirectory().appending(name)) + } else { + URL(fileURLWithPath: NSTemporaryDirectory().appending(name)) + } + let data = try Data(contentsOf: url) + let graphs = try JSONDecoder().decode(Graphs.self, from: data) + #expect(graphs.version == 2) + } + + @Test + func graphDescriptionDict() throws { + let description = try #require(Graph.description(nil, options: ["format": "graph/dict"] as NSDictionary)) + let dic = description.takeUnretainedValue() as! [String: AnyHashable] + #expect(dic["version"] as? UInt32 == 2) + #expect((dic["graphs"] as? NSArray)?.count == 0) + } + + @Test + func graphDescriptionDot() throws { + let options = NSMutableDictionary() + options["format"] = "graph/dot" + #expect(Graph.description(nil, options: options) == nil) + let graph = Graph() + let description = try #require(Graph.description(graph, options: options)) + let dotGraph = description.takeUnretainedValue() as! String + let expectedEmptyDotGraph = #""" + digraph { + } + + """# + #expect(dotGraph == expectedEmptyDotGraph) + } + + @Test + func graphCallback() { + let graph = Graph() + Graph.setUpdateCallback(graph, callback: nil) + Graph.setUpdateCallback(graph) { + print("Update") + } + Graph.setInvalidationCallback(graph, callback: nil) + Graph.setInvalidationCallback(graph) { attr in + print("Invalidate \(attr)") + } + + } + + @Test + func counter() { + let graph = Graph() + #expect(graph.mainUpdates == 0) + } + +} diff --git a/Tests/ComputeTests/Shared/Graph/SubgraphTests.swift b/Tests/ComputeTests/Shared/Graph/SubgraphTests.swift new file mode 100644 index 0000000..2f6999e --- /dev/null +++ b/Tests/ComputeTests/Shared/Graph/SubgraphTests.swift @@ -0,0 +1,27 @@ +import Testing + +@Suite +struct SubgraphTests { + + @Test + func shouldRecordTree() { + setenv("AG_TREE", "0", 1) + #expect(Subgraph.shouldRecordTree == false) + + Subgraph.setShouldRecordTree() + #expect(Subgraph.shouldRecordTree == true) + } + + @Test + func treeElementAPICheck() { + let graph = Graph() + let subgraph = Subgraph(graph: graph) + subgraph.apply { + let value = Attribute(value: ()) + Subgraph.beginTreeElement(value: value, flags: 0) + Subgraph.addTreeValue(value, forKey: "", flags: 0) + Subgraph.endTreeElement(value: value) + } + } + +} diff --git a/Tests/ComputeTests/Shared/readingStandardOutput.swift b/Tests/ComputeTests/Shared/readingStandardOutput.swift new file mode 100644 index 0000000..cc51a85 --- /dev/null +++ b/Tests/ComputeTests/Shared/readingStandardOutput.swift @@ -0,0 +1,97 @@ +import Foundation + +/// Additionally writes any data written to standard output into the given output stream. +/// +/// - Parameters: +/// - output: An output stream to receive the standard output text +/// - encoding: The encoding to use when converting standard output into text. +/// - body: A closure that is executed immediately. +/// - Returns: The return value, if any, of the `body` closure. +func printingStandardOutput( + to output: inout Target, + encoding: String.Encoding = .utf8, + body: () -> Result +) + async -> Result where Target: TextOutputStream +{ + var result: Result? = nil + + let consumer = Pipe() // reads from stdout + let producer = Pipe() // writes to stdout + + let stream = AsyncStream { continuation in + let clonedStandardOutput = dup(STDOUT_FILENO) + defer { + dup2(clonedStandardOutput, STDOUT_FILENO) + close(clonedStandardOutput) + } + + dup2(STDOUT_FILENO, producer.fileHandleForWriting.fileDescriptor) + dup2(consumer.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) + + consumer.fileHandleForReading.readabilityHandler = { fileHandle in + let chunk = fileHandle.availableData + if chunk.isEmpty { + continuation.finish() + } else { + continuation.yield(chunk) + producer.fileHandleForWriting.write(chunk) + } + } + + result = body() + try! consumer.fileHandleForWriting.close() + } + + for await chunk in stream { + output.write(String(data: chunk, encoding: encoding)!) + } + + return result! +} + +func printingStandardError( + to stream: inout Target, + encoding: String.Encoding = .utf8, + body: () -> Result +) async + -> Result where Target: TextOutputStream +{ + var result: Result? = nil + + let consumer = Pipe() // reads from stderr + let producer = Pipe() // writes to stderr + + let chunks = AsyncStream { continuation in + let clonedStandardError = dup(STDERR_FILENO) + defer { + dup2(clonedStandardError, STDERR_FILENO) + close(clonedStandardError) + } + + dup2(STDERR_FILENO, producer.fileHandleForWriting.fileDescriptor) + dup2(consumer.fileHandleForWriting.fileDescriptor, STDERR_FILENO) + + consumer.fileHandleForReading.readabilityHandler = { fileHandle in + let chunk = fileHandle.availableData + if chunk.isEmpty { + continuation.finish() + } else { + continuation.yield(chunk) + producer.fileHandleForWriting.write(chunk) + } + } + + result = body() + try! consumer.fileHandleForWriting.close() + } + + for await chunk in chunks { + guard let chunkString = String(data: chunk, encoding: encoding) else { + continue + } + stream.write(chunkString) + } + + return result! +}