diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..6b34001 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,5 @@ +ignore: + - "Source/library" # C library + - "Tests" # Test files + +max_report_age: off \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2e628c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# C libarary +Source/library/* linguist-vendored \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..d2ed08a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,38 @@ +
+
+ +## DO NOT Submit Non-bug Issue or Framework Question Here +Please go to [pcpLiu/SerranoExplore](https://github.com/pcpLiu/SerranoExplore) opening an issue for general support. This repo only accepts bug-related issues. + +
+
+ + +#### Description +(Give a overall description of the situation you have met) + + +#### Environment Information +- Serrano version: (XXXXX) +- Xcode Version: (XXXXX) +- Platform: (macOS or iOS) +- Platform System version: (iOSxxxx or macOSxxx) + + +#### Reproduce Steps +(A __gist__ or __github repo__ is prefered. Should be detailed enough to reproduce.) + + +#### Expecting behavior +(Describe what result or behavior should be expecting) + + +#### Actual behavior +(Describe actual result or behavior) + + +#### Possible Implementation (Optional) +(Optional implementation to fix this bug) \ No newline at end of file diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..c596e8c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,21 @@ +
+
+ +## DO NOT Submit Non-bug PR Here +Please go to [pcpLiu/SerranoExplore](https://github.com/pcpLiu/SerranoExplore) to discuss feature enhancements. This repo only accepts bug-related PRs. + +
+
+ + +#### Description +(Description of this PR) + +#### Related Issues +(List related issues for this PR) + +#### How Has This Been Tested? +(Give information how your tested the fixed code) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5363363 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +serrano.xcodeproj/xcuserdata +.testing +xcuserdata + +*.coverage.txt +*.xccoverage.plist + +.cov/ +build/ +DerivedData/ + +# auto generated local docs +docs/generated_docs/* diff --git a/.jazzy.yaml b/.jazzy.yaml new file mode 100644 index 0000000..840b4ca --- /dev/null +++ b/.jazzy.yaml @@ -0,0 +1,39 @@ +## This configure used to generate guides docs only. Not for API + + +xcodebuild_arguments: ['-scheme', 'TestingHostApp', '-project', 'serrano.xcodeproj'] +module: Serrano + +author: pcpLiu +author_url: https://github.com/pcpLiu +github_url: https://github.com/pcpLiu/Serrano +module_version: v0.1.0-alpha +github_file_prefix: https://github.com/pcpLiu/Serrano/tree/v0.1.0-alpha +root_url: http://serrano-lib.org/docs/v0.1.0-alpha/guides +theme: docs/fullwidth +output: docs/generated_docs/v0.1.0-alpha/guides + +# Generate guides only +exclude: Source/* + + +documentation: docs/guides/**/*.md +abstract: docs/sections/**/*.md' +readme: docs/sections/Home.md + + +# Layout +custom_categories: + - name: 'Getting started' + children: + - 'What is Serrano' + - 'Core concepts' + - 'VGG16 Example' + - name: 'Extension' + children: + - 'Write your own operator' + - 'Write your own optimizer' + - name: 'Contribution' + children: [Contribution] + - name: 'API Reference' + children: [API Reference] diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fd22b6d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +os: osx +osx_image: xcode9 +language: swift +xcode_sdk: + - iphonesimulator10.3 + - macosx10.12 + +branches: + only: + - master + +script: + - bash scripts/travisCI.sh \ No newline at end of file diff --git a/Examples/Graph/VGG16.swift b/Examples/Graph/VGG16.swift new file mode 100644 index 0000000..5c30f9d --- /dev/null +++ b/Examples/Graph/VGG16.swift @@ -0,0 +1,168 @@ +import XCTest +import Serrano + +/** +This code shows how to construct a VGG16 network using graph's low-level API. + +[vgg16](http://book.paddlepaddle.org/03.image_classification/image/vgg16.png) +*/ +func configureVGG16() -> ComputationGraph { + let g = ComputationGraph() + + // input [244, 244, 3] + let shape = TensorShape(dataType: .float, shape: [244, 244, 3]) + let input = g.tensor(shape: shape) + + // block 1 + let convOp = ConvOperator2D(numFilters: 64, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: input.shape) + let (out, _, _) = g.operation(inputs: [input], op: convOp) + + let convOp1 = ConvOperator2D(numFilters: 64, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out.first!.shape) + let (out1, _, _) = g.operation(inputs: out, op: convOp1) + + let poo1 = MaxPool2DOperator(kernelSize: [2, 2], + channelPosition: TensorChannelOrder.Last, + paddingMode: PaddingMode.Valid) + let (out_block_1, _, _) = g.operation(inputs: out1, op: poo1) + + // block 2 + let convOp2 = ConvOperator2D(numFilters: 128, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out_block_1.first!.shape) + let (out2, _, _) = g.operation(inputs: out_block_1, op: convOp2) + + let convOp3 = ConvOperator2D(numFilters: 128, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out2.first!.shape) + let (out3, _, _) = g.operation(inputs: out2, op: convOp3) + + let poo2 = MaxPool2DOperator(kernelSize: [2, 2], + channelPosition: TensorChannelOrder.Last, + paddingMode: PaddingMode.Valid) + let (out_block_2, _, _) = g.operation(inputs: out3, op: poo2) + + // block 3 + let convOp4 = ConvOperator2D(numFilters: 256, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out_block_2.first!.shape) + let (out4, _, _) = g.operation(inputs: out_block_2, op: convOp4) + + let convOp5 = ConvOperator2D(numFilters: 256, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out4.first!.shape) + let (out5, _, _) = g.operation(inputs: out4, op: convOp5) + + let convOp6 = ConvOperator2D(numFilters: 256, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out5.first!.shape) + let (out6, _, _) = g.operation(inputs: out5, op: convOp6) + + let poo3 = MaxPool2DOperator(kernelSize: [2, 2], + channelPosition: TensorChannelOrder.Last, + paddingMode: PaddingMode.Valid) + let (out_block_3, _, _) = g.operation(inputs: out6, op: poo3) + + // bloack 4 + let convOp7 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out_block_3.first!.shape) + let (out7, _, _) = g.operation(inputs: out_block_3, op: convOp7) + + let convOp8 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out7.first!.shape) + let (out8, _, _) = g.operation(inputs: out7, op: convOp8) + + let convOp9 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out8.first!.shape) + let (out9, _, _) = g.operation(inputs: out8, op: convOp9) + + let poo4 = MaxPool2DOperator(kernelSize: [2, 2], + channelPosition: TensorChannelOrder.Last, + paddingMode: PaddingMode.Valid) + let (out_block_4, _, _) = g.operation(inputs: out9, op: poo4) + + // block 5 + let convOp10 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out_block_4.first!.shape) + let (out10, _, _) = g.operation(inputs: out_block_4, op: convOp10) + + let convOp11 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out10.first!.shape) + let (out11, _, _) = g.operation(inputs: out10, op: convOp11) + + let convOp12 = ConvOperator2D(numFilters: 512, + kernelSize: [3, 3], + padMode: PaddingMode.Same, + channelPosition: TensorChannelOrder.Last, + inputShape: out11.first!.shape) + let (out12, _, _) = g.operation(inputs: out11, op: convOp12) + + let poo5 = MaxPool2DOperator(kernelSize: [2, 2], + channelPosition: TensorChannelOrder.Last, + paddingMode: PaddingMode.Valid) + let (out_block_5, _, _) = g.operation(inputs: out12, op: poo5) + + // block 6 + let fc13 = FullyconnectedOperator(inputDim: 25088, numUnits: 4096) + let (out13, _, _) = g.operation(inputs: out_block_5, op: fc13) + + let fc14 = FullyconnectedOperator(inputDim: 4096, numUnits: 4096) + let (out14, _, _) = g.operation(inputs: out13, op: fc14) + + let fc15 = FullyconnectedOperator(inputDim: 4096, numUnits: 4096) + let (out15, _, _) = g.operation(inputs: out14, op: fc15) + + return g +} + + + +class Example_VGG16: XCTestCase { + // Test vgg16 network forward + // Suggestion: run this function on macOS or real iOS devices supporting GPU. It goona be very slow on CPU mode. + func testVGG16Forawad() { + SerranoLogging.release = true + + let _ = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU) + let vgg16 = configureVGG16() + vgg16.allocateAllTensors() + vgg16.forwardPrepare() + + let start = CFAbsoluteTimeGetCurrent() +// vgg16.forward(mode: .CPU) + vgg16.forward(mode: .GPU) + print("Forward Execution Time : \((CFAbsoluteTimeGetCurrent() - start) * 100) ms") + } +} diff --git a/Examples/Graph/YOLO.swift b/Examples/Graph/YOLO.swift new file mode 100644 index 0000000..9f185f3 --- /dev/null +++ b/Examples/Graph/YOLO.swift @@ -0,0 +1,36 @@ +// +// YOLO.swift +// serrano +// +// Created by ZHONGHAO LIU on 10/25/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +class YOLO: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + + //TODO: +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6b22c72 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Zhonghao LIU + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab3da86 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +![logo](https://github.com/pcpLiu/Serrano/blob/master/logo.png) + +

+ + travisCI-building + + + coverage + + + iOS + + + macOS + + + License: MIT + + + Swift 3.2 + + + gitter + +

+ +## Serrano +Aiming to offering popular and cutting edge techs in deep learning area on iOS devices, Serrano is developed as a tool for developers & researchers with deep learning background to quickly implement their ideas on iOS devices. Meanwhile, it supports macOS as a pure swift framework bonus. + +## Features +- Implemented an efficient NDArray class [Tensor](http://serrano-lib.org/docs/v0.1.0-alpha/api/Classes/Tensor.html) which supports: + - CPU calculation with [BLAS](https://developer.apple.com/documentation/accelerate/blas)/[vecLib](https://developer.apple.com/documentation/accelerate/veclib)/[vDSP](https://developer.apple.com/documentation/accelerate/vdsp) for better performance + - GPU calculation on [no-copy MTLBuffer](https://developer.apple.com/documentation/metal/mtldevice/1433382-makebuffer) for memory saving +- Including common [operators](http://serrano-lib.org/docs/v0.1.0-alpha/api/Classes.html) for constructing various computation graphs and it is easy to [implement custom operators](). +- A python package [serrano-tools](https://github.com/pcpLiu/serrano-tools) is here to help convert your existing models (Under development) +- No third-party library dependent. Compatible from iOS 10. + +## Install + +#### Via CocoaPods +Install the latest version: +``` +pod 'Serrano', :git => 'https://github.com/pcpLiu/Serrano.git' +``` + + +#### Manually integrate into your workspace/project + +Download or clone Serrano and drag `serrano.xcodeproj` into your workspace or project. +Add `Serrano` into the `Target Dependencies` of your target. + + +## Docs +The APIs are hosted at [http://serrano-lib.org](http://serrano-lib.org/docs/v0.1.0-alpha/api/). + +Currently, we are adding more guides. + +## Questions && Issues + :bangbang: Please __only open [bug]/[feature request] related issues__ in THIS repo and follow this [issue guide](). :bangbang: + +__For any general issue/discussion || framework support__, please go to [pcpLiu/SerranoExplore](https://github.com/pcpLiu/SerranoExplore) opening an issue. Also you can discuss on [Gitter](https://gitter.im/SerranoFramework/Lobby) + + +## macOSX +Serrano was developed as an iOS framework. However, the framework could be added and used in Cocoa applications (macOS App) without effort. + +## Contribution +Contribution are wanted! And please read the [Contributing Guide]() before making a PR. + +## License +Serrano is liscensed under [MIT](https://github.com/pcpLiu/serrano/blob/master/LICENSE). Copyright (c) 2017, Zhonghao (Tim) Liu. + + +## Acknowledgement +Serrano are inspired and influenced by these open source projects: + +- [MXNET](https://github.com/apache/incubator-mxnet) +- [Keras](https://github.com/fchollet/keras) +- [TensorFlow](https://www.tensorflow.org/) +- [Caffe](https://github.com/BVLC/caffe) diff --git a/Serrano.podspec b/Serrano.podspec new file mode 100644 index 0000000..67df39a --- /dev/null +++ b/Serrano.podspec @@ -0,0 +1,26 @@ +Pod::Spec.new do |spec| + spec.name = 'Serrano' + spec.version = '0.1.1-alpha' + spec.license = { :type => 'MIT' } + spec.homepage = 'https://github.com/pcpLiu/Serrano' + spec.authors = { 'Tim Liu' => 'pcpliu.dev@gmail.com' } + spec.summary = 'Graph computation library for iOS' + spec.source = { :git => 'https://github.com/pcpLiu/Serrano.git', :tag => 'v0.1.1-alpha' } + spec.module_name = 'Serrano' + spec.documentation_url = 'http://serrano-lib.org' + + spec.ios.deployment_target = '10.0' + spec.osx.deployment_target = '10.11' + spec.requires_arc = true + + spec.framework = 'Accelerate' + spec.weak_framework = 'Metal' + + spec.source_files = 'Source/**/*.{swift,c,h}' + spec.public_header_files = 'Source/SupportingFiles/Serrano.h' + spec.resources = 'Source/**/*.{metal}' + + spec.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.2', 'SWIFT_INCLUDE_PATHS' => '$(SRCROOT)/Serrano/Source/library/FBSUtil/**'} + spec.preserve_paths = 'Source/library/FBSUtil/module.modulemap' + +end \ No newline at end of file diff --git a/Source/Serrano/core/engine.swift b/Source/Serrano/core/engine.swift new file mode 100644 index 0000000..5cdc678 --- /dev/null +++ b/Source/Serrano/core/engine.swift @@ -0,0 +1,238 @@ +// +// engine.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/3/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal + +//public enum SerranoBackend { +// case Serrano +// //case CoreML +//} + + +/** + This class is supposed be initialized and configured at the very beggining of your app. + It is responsible for setting up computation envirionment involving with iOS's GPU device. + It will initialize serveral instances which will involve with heavy GPU evaluation and are reconmmended reusing by [Apple documentation](https://developer.apple.com/library/content/documentation/3DDrawing/Conceptual/MTLBestPracticesGuide/index.html). + + The `configuredEngine` is a _singleton_ instance and should be used everywhere you need to access `GPUDevice` (an instance of `MTLDevice`),\ + `serranoCommandQueue` (an instance of `MTLCommandQueue`). + + Initial and configure a GPU engine: + + ````swift + let configuredEngine = SerranoEngine.configuredEngine + let (success, message) = configuredEngine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + if !success { + // Failed to congiure GPU device + print(message) + } + ```` + + Initial and configure a CPU engine: + ````swift + let configuredEngine = SerranoEngine.configuredEngine + configuredEngine.configureEngine(computationMode: .CPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + ```` + */ +public class SerranoEngine { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Instance + public static let configuredEngine = SerranoEngine() + + /// GPU device. + public var GPUDevice: MTLDevice? + + /// Metal Command Queue for serrano. + public var serranoCommandQueue: MTLCommandQueue? + + /// Engine computation mode + public var computationMode: OperatorComputationMode + + /// Loaded MTLCompute​Pipeline​States. + /// A dictionary with `label` as key and corresponding `MTLComputePipelineState` as value. + public var loadedGPUKernels: [String : MTLComputePipelineState] + + /// METAL library + public var metalLibrary: MTLLibrary? + +// /// Backend compuation +// public var backend: SerranoBackend = SerranoBackend.Serrano + + /// Default operator computation Mode + public var defaultComputationMode: OperatorComputationMode = .Auto + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Mark: - Initializer + + private init() { + self.GPUDevice = nil + self.serranoCommandQueue = nil + self.computationMode = .CPU + self.loadedGPUKernels = [:] + self.metalLibrary = nil + } + + /** + Setup `GPUDevice`, `serranoCommandQueue` and `computationMode` of engine. + + - Parameters: + - computationMode: one of choies in enum `OperatorComputationMode` (`GPU` or `CPU`). + - serranoCommandQueue: Optional. If it is `nil`, method will initialize a command queue in `GPU` mode. + - serranoMTLLibrary: Optional. If it is `nil`, method will initialize a default `MTLLibrary`. + - systemDefaultGPUDevice: Optional. If this is nil and `computationMode` is `GPU`,\ + method will try to create a instance calling `MTLCreate​System​Default​Device()`.\ + If failed to initialize a GPU device instance, will return `false`. + + - Returns: + - result: If configure engine successfully. + - message: Message information of configure. + + - Note: When user gives no device instance and method fails to initialized a GPU device instance, \ + `Serrano` will automatically set up `computationMode` to `OperatorComputationMode.CPU`. + + - Warning: This method must be called before doing any GPU related computation. + + */ + public func configureEngine(computationMode mode: OperatorComputationMode, + serranoCommandQueue commandQueue: MTLCommandQueue? = nil, + serranoMTLLibrary library: MTLLibrary? = nil, + systemDefaultGPUDevice gpu: MTLDevice? = nil) -> (result: Bool, message: String){ + if mode == .GPU { + // device + if gpu == nil { + self.GPUDevice = MTLCreateSystemDefaultDevice() + + guard (self.GPUDevice != nil) else { + self.computationMode = .CPU + SerranoLogging.warningLogging(message: "Failed to create a MTLDevice instance from MTLCreateSystemDefaultDevice().", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return (false, "Failed to create a MTLDevice instance from MTLCreateSystemDefaultDevice().") + } + } else { + self.GPUDevice = gpu + } + + // computeCommandQueue + if commandQueue == nil { + self.serranoCommandQueue = self.GPUDevice!.makeCommandQueue() + } else { + self.serranoCommandQueue = commandQueue + } + + // default library + if library == nil { + // build directly + var libpath = Bundle(for: type(of: self)).path(forResource: "default", ofType: "metallib") + if libpath == nil { + // build through cocoapod + libpath = Bundle(identifier: "SerranoMetalLib")?.path(forResource: "default", ofType: "metallib") + } + guard libpath != nil else { + return (false, "Failed to locate default.metlib") + } + do { + try self.metalLibrary = self.GPUDevice!.makeLibrary(filepath: libpath!) + } catch { + return (false, "Failed to create the default metal library. Erro:\n\(error)") + } + } else { + library!.label = "serranoMetalLibrary" + self.metalLibrary = library! + } + + self.computationMode = .GPU + return (true, "Setup engine computation mode to \(self.computationMode) with device: \(self.GPUDevice!.description).") + } else { + self.computationMode = .CPU + return (true, "Setup engine computation mode to \(self.computationMode).") + } + + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Mark: - Methods + + /** + Reset all attributes of engine to default values. + */ + public func resetEngine() { + self.GPUDevice = nil + self.computationMode = .CPU + self.loadedGPUKernels = [:] + self.metalLibrary = nil + self.serranoCommandQueue = nil + } + + /** + Check if current configured engine has available GPU device. + + - Returns: `true` if has available device. + */ + public func hasAvailableGPU() -> Bool { + return self.GPUDevice != nil + } + + /** + Get a kernel in `loadedGPUKernels`, if not in loaded kernels, return `nil` + + - Parameters + - label: label of target `MTLComputePipelineState` + */ + internal func getGPUKernelFromLoadedKernels(kenelLabel label: String) -> MTLComputePipelineState? { + return self.loadedGPUKernels[label] + } + + /** + Load GPU compute kernel from Serrano's default Metal library. + Before loading method will check if already loaded this kernel, if loaded just return the kernel + If not found in `loadedGPUKernels`, method will create a new `MTLCompute​Pipeline​State` instance for function with target `label` and return the kernel. + When failed to find the function, will return a `nil` kernel with error information. + + - Parameters: + - label: Kernel function name in Metal file. + + - Returns: `kernel`: Optional. Target kernel. Will be `nil` if fails to load; `message`: message information. + + - Note: User must configure the engine first before loading any GPU kernels. If method could find `GPUDevice` or 'metalLibrary' is `nil`, it will + raise a fatal error. + */ + public func loadGPUKernel(kernelLabel label: String) -> (result: MTLComputePipelineState?, message: String){ + guard self.GPUDevice != nil && self.metalLibrary != nil else { + fatalError("[Serrano]Trying to load GPU kernel without initializing a GPU device or Metal Library.") + } + + var kernel: MTLComputePipelineState? = self.getGPUKernelFromLoadedKernels(kenelLabel: label) + if kernel != nil { + return (kernel!, "Successfully loaded GPU kernel \(label).") + } + + let function = self.metalLibrary!.makeFunction(name: label) + if function == nil { + return (nil, "Could not find the Metal kernel function: \(label).") + } + + + do { + try kernel = self.GPUDevice!.makeComputePipelineState(function: function!) + } + catch { + return (nil, "Error catched when tring to load kernel \(label). Details:\n\(error)") + } + + + self.loadedGPUKernels[label] = kernel! + return (kernel!, "Successfully loaded GPU kernel \(label).") + } +} + + + diff --git a/Source/Serrano/core/graph.swift b/Source/Serrano/core/graph.swift new file mode 100644 index 0000000..b70bc98 --- /dev/null +++ b/Source/Serrano/core/graph.swift @@ -0,0 +1,114 @@ +// +// compute_graph.swift +// serrano +// +// +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +public protocol GraphSupportedBindingDataType {} + +extension Array: GraphSupportedBindingDataType {} +extension Tensor: GraphSupportedBindingDataType {} +extension Float: GraphSupportedBindingDataType {} +extension Double: GraphSupportedBindingDataType {} +extension Int: GraphSupportedBindingDataType {} + +/** +Basic graph protocol +*/ +public protocol Graph { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// List of `GraphSymbol`. + /// Key is the `UID` of symbol object in value field. + var symbols: [String: GraphSymbol] {get set} + + /// The readable label of this graph + var graphLabel: String {get set} + + /// If this graph is trainable. + var trainable: Bool {get set} + + /// Description of this graph + var description: String {get} + + /// Optimizer of this graph doing backward training. + /// Could be `nil` if just do forward calcualtion. + var optimizer: Optimizer? {get set} + + /// Counter of backward training + var epoch: Int {get} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Create symbols + + + /// Add a `TensorSymbol` to the graph. + /// - Parameters: + /// - label: label description + /// - shape: shape description + /// - Returns: return value description + func tensor(_ label: String?, shape: TensorShape) -> TensorSymbol + + /// Add a `ScalarSymbol` to the graph. + /// + /// - Parameter label: label + /// - Returns: A `ScalarSymbol` + func scalar(_ label: String?, dataType: TensorDataType) -> ScalarSymbol + + /// Add a `OperatorSymbol` to the graph. + /// + /// - Parameters: + /// - inputs: input array of `TensorSymbol` + /// - op: A `ComputableOperator` instance + /// - Returns: Output `TensorSymbol` from `operator` calculation, and constructed `OperatorSymbol` + func operation(_ label: String?, inputs: [TensorSymbol], op: ComputableOperator) -> (outputTensorSymbols: [TensorSymbol], operatorSymbol: OperatorSymbol, paramSymbols: [GraphSymbol]) + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - compute + + /// Forward computing from inputs to outputs. + /// + /// - Parameter mode: computation mode + /// - Returns: Array of tensors/scalars. `nil` if met errors. + func forward(mode: OperatorComputationMode) -> [DataSymbolSupportedDataType]? + + /// Backward computing the grads for updatable data symbols. + /// + /// - Parameters: + /// - mode: computation mode + func backward(mode: OperatorComputationMode) + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - other + + /// Bind data to `TensorSymbol`. + /// + /// - Parameter data: A dictinary whose key is `label` of a `TensorSymbol` + /// and value is an array of `DataSymbolSupportedDataType` objects. + func bindData(_ data: [String: DataSymbolSupportedDataType]) + + /// Add symbol to `symbols`. + /// Should check duplicate + /// + /// - Parameter symbol: new symbol + func addSymbols(_ symbol: GraphSymbol) +} + +extension Graph { + /// Add symbol to `symbols`. + /// Should check duplicate + /// + /// - Parameter symbol: new symbol + public func addSymbols(_ symbol: GraphSymbol) { + if self.symbols[symbol.UID] == nil { + var g = self as Graph + g.symbols[symbol.UID] = symbol + } + } +} diff --git a/Source/Serrano/core/model.swift b/Source/Serrano/core/model.swift new file mode 100644 index 0000000..8483db8 --- /dev/null +++ b/Source/Serrano/core/model.swift @@ -0,0 +1,68 @@ +// +// model.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/4/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + +/** +ModelCallback defines a serires of APIs a callback object for models. +*/ +public protocol ModelCallback { + // TODO: IMPLEMENT +} + +/** +This protocol defines higher-level APIs for creating, training and prediction of a model. +*/ +public protocol Model: Graph { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator symbols of this model + var operators: [OperatorSymbol] {get} + + /// List of input tensor symbols + var inputs: [TensorSymbol] {get} + + /// List of output tensor symbol + var outputs: [TensorSymbol] {get} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Constructing models + + /// Add an input entry for moedel + /// + /// ## inputShape + /// `inputShape` indicates the shape of a sample without batch index. + /// For example if we have some 128x128 RGB picture as input, the `inputShape` should be `TensorShape` + /// object with `shapeArray`: `[128, 128, 3]` if `channelOrder` is 'TensorChannelOrder.last' + /// + /// - Parameter inputShape: input shape of sample. Not include batch index. + /// - Returns: tensor symbol representation + func addInput(inputShape: TensorShape, channelOrder: TensorChannelOrder) -> TensorSymbol + + /// Add layer to the model. + /// + /// - Parameters: + /// - inputs: list of input tensor symbols to this layer + /// - op: operator + /// - Returns: list of output tensor symbols from this layer + func addLayer(_ inputs: [TensorSymbol], op: ComputableOperator) -> [TensorSymbol] + + func configure() + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Training + + // TODO: IMPLEMENT + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Prediction + + // TODO: IMPLEMENT + +} diff --git a/Source/Serrano/core/operator.swift b/Source/Serrano/core/operator.swift new file mode 100644 index 0000000..6562009 --- /dev/null +++ b/Source/Serrano/core/operator.swift @@ -0,0 +1,207 @@ +// +// Operator.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/17/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal + +/** +The delegate support methods tracking the calculation status and result of associated `Operator` object. +Operator could assign attribute `computationDelegate` to a instance conforms to this protocol. +The instanced could track the calculation status of operator. + */ +public protocol OperatorCalculationDelegate { + /** + Tell the delegate this `Operator` will begin to calcualte the output tensor + + - Parameters: + - op: The calculation operator + */ + func operatorWillBeginComputation(_ op: ComputableOperator) + + /** + Tell the delegate this `Operator` has done the calcualtion. + + - Parameters: + - op: The calcaulation operator. + - tensor: The calcualted output tensors object. + */ + func operatorDidEndComputation(_ op: ComputableOperator, outputTensors tensors: [Tensor]) + + /// Tell the delegate this operator will begin grads calculation + /// + /// - Parameter op: op + func operatorWillBeginGradsComputation(_ op: ComputableOperator) + + /// Tell the delegate this operator end grads claculation + /// + /// - Parameters: + /// - op: op + /// - tensors: grads tensor + func operatorDidEndGradsComputation(_ op: ComputableOperator, grads: [String: DataSymbolSupportedDataType]) + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +public enum OperatorComputationMode { + case CPU + case GPU + case Auto +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +public class OperatorUtils { + + /// If target operator's `computationDelegate` is `nil`, logging warning. + /// + /// - Parameters: + /// - op: target operator conforms to `ComputableOperator` + /// - file: checking file name + /// - function: checking function name + /// - line: checking code line number + public static func delegateNilWarning(op: T, file: String, function: String, line: Int) { + if op.computationDelegate == nil { + SerranoLogging.warningLogging(message: "Call async computation without assigning computate delegate. The output will never be fetched.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/// Types of operator's input-output mapping. +public enum OperatorMappingType { + /// N-to-N + case OneToOne + + /// N-to-1 + case Constant +} + + +/** + This protocol defines the common computation APIs of `Operator`. + */ +public protocol ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// MARK: - Attributes + + /// Computation delegate. + /// + /// The assigned delegate can track the computation status and \ + /// result through methods from `OperatorComputationDelegate`. + /// + /// Usually the delegate is a `Flow` object. + var computationDelegate: OperatorCalculationDelegate? {get set} + + /// Kernel function name + var metalKernelFuncLabel: String {get} + + /// Operator readable label + var operatorLabel: String {get set} + + /// Input tensors to operate + var inputTensors: [Tensor]? {get set} + + /// Output tensors + var outputTensors: [Tensor]? {get set} + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + var disableInputOutputCheck: Bool {get set} + + /// Indicate if this operator would do paramter update + var trainable: Bool {get set} + + /// The mapping type of this operator + var mapType: OperatorMappingType {get} + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// MARK: - Computation realted methods (forward) + + /** + Calulate the output tensor shape given an input tensor shape. + If the operator cannot operate on the input tensor shape, return `nil`. + + - Parameters: + - shapeArray: An array of `TensorShape` + + - Returns: A `TensorShape` object or `nil` if the operator could not operate on the input shape. + */ + func outputShape(shapeArray shapes:[TensorShape]) -> [TensorShape]? + + /// Check if the input tensors and output tensors's shape matching + /// + /// - Returns: `check` is `true` if match; `msg` error info if not match + func inputOutputTensorsCheck() -> (check: Bool, msg: String) + + /// Compute sync + /// + /// - Parameter computationMode: computationMode + func compute(_ computationMode: OperatorComputationMode) + + /// Compute the output tensor asyncally. Output result will be passed to `computationDelegate`. + /// + /// - note: If the `computationDelegate` is nil, the computed output will be lost. + func computeAsync(_ computationMode: OperatorComputationMode) + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// MARK: - Differentiaion realted methods (backward) + + + /// Compute grads from output against each input tensor and involving parameters. + /// + /// ## Identify corresponding input + /// The returned label of data could be used to identify its correspoding input + /// following below rules: + /// - __Input tensor__. `input_{i}` where `i` is the corresponding input tensor's index in `inputTensors` + /// - __Parameter__. The parameter's name. + /// + /// - Note: Operator will not store grads tensor. If the returned value not used, + /// grads will lost. + /// + /// - Parameter computationMode: computationMode description + /// - Returns: grads list for each input tensor and involving parameters with label + func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] + + /// Compute async grads from output against each input tensor and involving parameters. + /// + /// - Parameter computationMode: computationMode + /// - Parameter upGrds: Optional. Grads from upstream operators in a Graph computation. + func gradComputAsync(_ computationMode: OperatorComputationMode) + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /// MARK: - Support symbolic graph computation + + /// This function is called when add an operator to a `Graph` with function `operation()`. + /// A `Graph` object is returned by this function representing the inner structure of this operator. + /// Some complex operators may consist of other simple operators and we want explicitly show the structure + /// in the graph it added to. + /// So the returned graph will be merged into the graph calling this function. + /// + /// - Parameter InputSymbols: input symbols for this graph + /// - Returns: a graph object +// func addedToGraph(with InputSymbols: [TensorSymbol]) -> Graph + + /// Bind data from symbol to parameter of this operator. + /// + /// - Parameters: + /// - symbols: binded symbols + func bindParamSymbols(_ symbols: [GraphSymbol]) + + /// An array of `GraphSymbol` for this operator's parameters. + /// This array may be empty if operator needs no parameter. + /// This function is used in constructing computaion graph. + /// + /// - Returns: An array. + func paramSymbols() -> [GraphSymbol] + +} + diff --git a/Source/Serrano/core/optimizer.swift b/Source/Serrano/core/optimizer.swift new file mode 100644 index 0000000..954d252 --- /dev/null +++ b/Source/Serrano/core/optimizer.swift @@ -0,0 +1,69 @@ +// +// optimizer.swift +// serrano +// +// Created by ZHONGHAO LIU on 9/26/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + +/// Learning rate decay method +public enum LearningRateDecayMethod { + /// Each epoch update following: `lr = lr_initial - decay * t` where `t` is current epoch.. + case Step + + /// Each epoch udpate following: `lr = lr_initial * e^(-decay * t)` where `t` is current epoch. + case Exponential + + /// Each epoch udpate following: `lr = lr_initial /(1 + decay * t)` where `t` is current epoch. + case Inverse + + + /// Calculate decayed lr for current epoch. + /// + /// - Parameters: + /// - initialLR: initial lr + /// - decay: decay hyperparameter + /// - Returns: decayed lr + func decayLR(initialLR: Float, decay: Float, epoch: Int) -> Float { + var lr: Float = 0.0 + if self == LearningRateDecayMethod.Step { + lr = initialLR - decay * Float(epoch) + } else if self == LearningRateDecayMethod.Exponential { + lr = initialLR * exp(-decay*Float(epoch)) + } else { + lr = initialLR / (1 + decay * Float(epoch)) + } + return lr + } +} + + + +/** +This protocol defines the API and behavior of an optimizer. +*/ +public protocol Optimizer { + //// Initial set learning reate + var initLearningRate: Float {get} + + /// Learning rate of current epoch + var learningRate: Float {set get} + + /// Decay method + var decayMethod: LearningRateDecayMethod {get set} + + /// Do preapre work before 1st backward if needed. + func prepare(_ graph: Graph) + + /// Update a data symbol's updated value + /// + //// - Parameters: + /// - dataSymbol: target symbol + /// - gradValue: gradvalue fot this time updating + func updateParameter(_ dataSymbol: DataSymbol, gradValue: DataSymbolSupportedDataType) +} + +extension Optimizer { +} diff --git a/Source/Serrano/core/resource_manager.swift b/Source/Serrano/core/resource_manager.swift new file mode 100644 index 0000000..fe1a9ed --- /dev/null +++ b/Source/Serrano/core/resource_manager.swift @@ -0,0 +1,430 @@ +// +// resource_manager.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/15/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +/** +A `MTLBufferResource` contains the allocated `MTLBuffer` related information for a `Tensor` object managed by a resource manager. +*/ +public struct MTLBufferResource { + /// `MTLBuffer` object + var buffer: MTLBuffer + + /// Offset from the buffer base address. + /// Used by sliced tensor object. + var offset: Int +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +public enum SerranoTensorStatus { + case Idle + case Occupy +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +/** +This is a framework level class +*/ +public class SerranoResourceManager { + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Table tracking allocated Tensor and its corresponding MTLBuffer + public var tensorBufferTable: [Tensor: MTLBuffer] + + /// The dictionary tracking the usage status of tensor + public var tensorStatusTable: [Tensor: SerranoTensorStatus] + + /// The operation queue when operate on `tensorBufferTable` and `tensorStatus` + public var operationQueue: DispatchQueue + + /// Readable label + public var label: String + + /// Description + public var description: String { + get { + return "SerranoResourceManager(label: \(self.label))" + } + } + + // Global resource manager + public static let globalManager = SerranoResourceManager(label: "gloabal_resource_manager") + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + public init(label: String = "Resource Manager") { + self.tensorBufferTable = [Tensor: MTLBuffer]() + self.tensorStatusTable = [Tensor: SerranoTensorStatus]() + self.operationQueue = DispatchQueue(label: "serrano.resourceQueue") + self.label = label + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Allocate unmanaged tensors + + + /// Allocate unmanaged tensors for shapes. + /// `Unmanaged` means that the alloated tensors will not be hold via strong refrence by manager. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func allocateUnamangedTensors(_ shapes: [TensorShape]) -> [Tensor] { + var tensors = [Tensor]() + for shape in shapes { + tensors.append(Tensor(repeatingValue: 0.0, tensorShape: shape)) + } + return tensors + } + + + /// Allocate unmanaged tensors for shapes. + /// `Unmanaged` means that the alloated tensors will not be hold via strong refrence by manager. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func allocateUnamangedTensor(_ shape: TensorShape) -> Tensor { + let tensor = Tensor(repeatingValue: 0.0, tensorShape: shape) + SerranoLogging.stdLogging(message: "Allocate unmanged tensor \(tensor.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.LowLevel) + return tensor + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Allocate unmanaged MTLBuffers + + /// Allocate unamanged `MTLBuffers`. + /// + /// - Warning: If engine has no available GPU device, `fatalError` raised. + /// + /// - Parameter tensors: target tensors + /// - Returns: Array of `MTLBuffer` + public func allocateUnmanagedMTLBuffers(_ tensors: [Tensor]) -> [MTLBuffer] { + // check gpu available + guard SerranoEngine.configuredEngine.hasAvailableGPU() else { + SerranoLogging.errorLogging(message: "No available GPU device.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + var buffers = [MTLBuffer]() + for tensor in tensors { + let newBuffer = SerranoEngine.configuredEngine.GPUDevice!.makeBuffer(bytesNoCopy: tensor.contentsAddress, + length: tensor.allocatedBytes, + options: MTLResourceOptions.storageModeShared) + buffers.append(newBuffer) + } + return buffers + } + + /// Allocate unamanged `MTLBuffer`. + /// + /// - Warning: If engine has no available GPU device, `fatalError` raised. + /// + /// - Parameter tensor: target tensor + /// - Returns: `MTLBuffer` + public func allocateUnmanagedMTLBuffe(_ tensor: Tensor) -> MTLBuffer { + return self.allocateUnmanagedMTLBuffers([tensor]).first! + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Allocate managed tensors + + + /// Request managed tensors for target shapes. + /// + /// - Note: All tensors requested by this method should be __return__ manually by calling + /// `returnTensors(_ tensors: [Tensor])` or `returnTensor(_ tensor: Tensor)` + /// + /// - Warning: This method is desgined for framework internal usage. Usually user should not + /// call this method. + /// + /// - Parameter shapes: target shapes + /// - Returns: tensors + public func allocateTensors(_ shapes: [TensorShape]) -> [Tensor] { + var tensors = [Tensor]() + for shape in shapes { + let candidate = self.tensorStatusTable.index(where: { (element) -> Bool in + return element.key.capacity >= shape.count && element.value == SerranoTensorStatus.Idle + }) + + if candidate == nil { + // didn't find a reuseable tensor, spawn a new tensor + let newTensor = Tensor(repeatingValue: 0.0, tensorShape: shape) + self.tensorStatusTable[newTensor] = SerranoTensorStatus.Occupy + tensors.append(newTensor) + + // initial a new MTLBuffer for this new Tensor in advance if has available GPU + if SerranoEngine.configuredEngine.hasAvailableGPU() { + let buffer = SerranoEngine.configuredEngine.GPUDevice!.makeBuffer(bytesNoCopy: newTensor.contentsAddress, + length: newTensor.allocatedBytes, + options: MTLResourceOptions.storageModeShared) + self.tensorBufferTable[newTensor] = buffer + } + + SerranoLogging.stdLogging(message: "\(self.description) allocated a new tensor [\(newTensor.description)] to target shape \(shape.shapeArray).", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: .LowLevel) + } else { + // reuse + let tensor = self.tensorStatusTable[candidate!].key + self.tensorStatusTable[tensor] = SerranoTensorStatus.Occupy + tensor.shape = shape + tensors.append(tensor) + SerranoLogging.stdLogging(message: "\(self.description) reused existing tensor [\(tensor.description)] to target shape \(shape.shapeArray).", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: .LowLevel) + } + + } + + return tensors + } + + + /// Allocate single tensor object. This function actually call `allocateTensors(forShapes shapes: [TensorShape])`. + /// + /// - Note: All tensors requested by this method should be __return__ manually by calling + /// `returnTensors(_ tensors: [Tensor])` or `returnTensor(_ tensor: Tensor)` + /// + /// - Warning: This method is desgined for framework internal usage. Usually user should not + /// call this method. + /// + /// - Parameter tensorShape: shape + /// - Returns: tensor + public func allocateTensor(_ tensorShape: TensorShape) -> Tensor { + return self.allocateTensors([tensorShape]).first! + } + + + /// Return managed tensors. + /// + /// - Note: Tensor not managed by this manager or is a slice tensor will be ignored. + /// + /// - Parameter tensors: returned tensors + public func returnTensors(_ tensors: [Tensor]) { + for tensor in tensors { + guard self.isManagingTensor(tensor) else { + SerranoLogging.warningLogging(message: "Return a tensor NOT allocated by resource manager.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + continue + } + + if tensor.isSliceTensor { + SerranoLogging.warningLogging(message: "Received a slice tensor. Only root tensor can be returned.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + continue + } + + self.operationQueue.sync { + self.tensorStatusTable[tensor] = SerranoTensorStatus.Idle + } + + SerranoLogging.stdLogging(message: "Return tensor \(tensor.description) to \(self.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: .LowLevel) + } + + } + + /// Return single tensor to resource manager + /// + /// - Parameter tensor: tensor + public func returnTensor(_ tensor: Tensor) { + self.returnTensors([tensor]) + } + + /// Release target tensors. + /// Actually clear corresponding entries in `tensorStatusTable` and `tensorBufferTable`. + /// + /// - Note: Tensor not managed by this manager or is a slice tensor will be ignored. + /// + /// - Parameter tensors: target tensors + public func releaseTensors(_ tensors: [Tensor]) { + for tensor in tensors { + // not managing, ignore + if !self.isManagingTensor(tensor) { + SerranoLogging.warningLogging(message: "Trying to release a tensor \(tensor.description) not managed by this resource manager: [\(self.description)]", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + continue + } + + // slice tensor, ignore + if tensor.isSliceTensor { + SerranoLogging.warningLogging(message: "Trying to release a slice tensor \(tensor.description).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + continue + } + + // remove entry + let _ = self.tensorStatusTable.remove(at: self.tensorStatusTable.index(forKey: tensor)!) + + // also release attached MTLBuffer if has + if self.tensorBufferTable[tensor] != nil{ + self.tensorBufferTable.remove(at: self.tensorBufferTable.index(forKey: tensor)!) + SerranoLogging.stdLogging(message: "Remove tensor \(tensor.description) from resource manager: \(self.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.LowLevel) + } + } + + } + + /// Check if a managed tensor is availabel for reuse + /// If the passed in tensor is a sliced tensor, we check the status of its root tensor. + /// + /// - Note: `false` will be returned if `tensor` is not managed by this manager. + public func isTensorAvailable(_ tensor: Tensor) -> Bool { + guard self.isManagingTensor(tensor) else { + SerranoLogging.warningLogging(message: "Trying to check status of tensor [\(tensor.description)], but tensor was not managed by \(self.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } + + var result: Bool = true + if tensor.isSliceTensor { + result = self.tensorStatusTable[tensor.sliceRootTensor!]! == SerranoTensorStatus.Idle + } else { + result = self.tensorStatusTable[tensor]! == SerranoTensorStatus.Idle + } + + return result + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Allocate MTLBufferResource + + /// Request `MTLBufferResource` for tensors. + /// + /// - Note: If the passed in tensors are not mamaged by this resource manager, + /// it will just call `allocateUnmanagedMTLBuffers(_ tensors: [Tensor])`. + /// + /// - Parameter tensors: target tensors + /// - Returns: Array of `MTLBufferResource` + public func allocateMTLBufferResources(_ tensors: [Tensor]) -> [MTLBufferResource] { + // check gpu available + guard SerranoEngine.configuredEngine.hasAvailableGPU() else { + SerranoLogging.errorLogging(message: "No available GPU device.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + var bufferResources = [MTLBufferResource]() + for tensor in tensors { + if !self.isManagingTensor(tensor){ + let buffer = self.allocateUnmanagedMTLBuffe(tensor) + bufferResources.append(MTLBufferResource(buffer: buffer, offset: 0)) + } else { + var buffer: MTLBuffer? + var offset = 0 + var targetTensor = tensor + + // slice tensor just uses root tensor's buffer with offset + if tensor.isSliceTensor { + targetTensor = tensor.sliceRootTensor! + offset = tensor.bytesOffsetFromRootTensor()! + } + + // get MTLBuffer + self.operationQueue.sync { + if self.tensorBufferTable[targetTensor] == nil { + // new buffer + buffer = SerranoEngine.configuredEngine.GPUDevice!.makeBuffer(bytesNoCopy: targetTensor.contentsAddress, + length: targetTensor.allocatedBytes, + options: MTLResourceOptions.storageModeShared) + // add entry + self.tensorBufferTable[targetTensor] = buffer + } else { + buffer = self.tensorBufferTable[targetTensor]! + } + } + + bufferResources.append(MTLBufferResource(buffer: buffer!, offset: offset)) + } + } + return bufferResources + } + + /// Request `MTLBufferResource` for a tensor. + /// + /// - Note: If the passed in tensor is not mamaged by this resource manager, + /// it will just call `allocateUnmanagedMTLBuffer(_ tensor: Tensor)`. + /// + /// - Parameter tensors: target tensor + /// - Returns: `MTLBufferResource` + public func allocateMTLBufferResource(_ tensor: Tensor) -> MTLBufferResource { + return self.allocateMTLBufferResources([tensor]).first! + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Release managed resources + + /// Release all managed resources + /// + /// - Warning: This function __does not__ guranteen that all managed tensors and buffers would be released in memory, + /// since if tensor or buffer objects are still used in other places, the ARC will nor clear it. + /// Before calling this function, the caller must be clear that all managed resources are in idle states. + public func releaseAllResources() { + self.operationQueue.sync { + self.tensorBufferTable.removeAll() + self.tensorStatusTable.removeAll() + } + SerranoLogging.stdLogging(message: "Resource manager: \(self.description) release all managed tensors and buffers", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.LowLevel) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Check if tensor is managed by this manager. + /// If a tensor is managed by this manager, all its sliced tensors are managed by this manager. + /// + /// - Note: If the tensor is a sliced tensor, we will check if managing its root tensor. + /// + /// - Parameter tensor: tensor description + /// - Returns: return value description + public func isManagingTensor(_ tensor: Tensor) -> Bool { + var result:Bool = true + var checkTensor: Tensor = tensor + if tensor.isSliceTensor { checkTensor = tensor.sliceRootTensor! } + // check + self.operationQueue.sync { + result = self.tensorStatusTable[checkTensor] != nil + } + return result + } + + /// Check if a MTLBuffer is managed by this manager + /// + /// - Parameter buffer: buffer description + /// - Returns: return value description + public func isManagingBufferr(_ buffer: MTLBuffer) -> Bool { + var result: Bool = false + self.operationQueue.sync { + result = self.tensorBufferTable.index(where: { (element) -> Bool in + return element.value.contents() == buffer.contents() + }) != nil + } + return result + } +} diff --git a/Source/Serrano/core/symbol.swift b/Source/Serrano/core/symbol.swift new file mode 100644 index 0000000..30d7ece --- /dev/null +++ b/Source/Serrano/core/symbol.swift @@ -0,0 +1,432 @@ +// +// symbol.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/// Description +public enum SymbolType { + case Tensor + case Operator + case Scalar + + /// If enum is data symbol + /// + /// - Returns: return value description + func isDataSymbol() -> Bool { + return self == SymbolType.Tensor || self == SymbolType.Scalar + } +} + + + + +/// The Data source of a data symbol (Tensor or scalar symbol) +public enum SymbolDataSource { + /// Symbol explicitly created by user and needs feeding from user + case User + + /// Symbol gets data from a calculation result, so no need sfeeding from user + case Calculation + + /// Symbol has a default value but also can receive data from user, + /// like some parameters of operators. + case Default + + /// other cases + case Other +} + +/// Generate 6-length random string from `[a-zA-Z0-9]` +/// +/// - Returns: ID +public func serranoSymbolUIDGenerate() -> String { + let IDLength = 6 + let letters: NSString = "9876543210abcdefghijklMNOPQRSTUVWXYZ0123456789mnopqrstuvwxyzABCDEFGHIJKL0123456789" + let len = UInt32(letters.length) + + var randomString = "" + + for _ in 0.. Graph + + /// Add new symbol to `inBounds`. + /// Should check duplicate. + func addToInBound(_ symbol: GraphSymbol) + + /// Add new symbol to `outBounds` + /// Should check duplicate. + func addToOutBound(_ symbol: GraphSymbol) + + /// Evaluate the symbol to get value. + /// Returns maybe `nil`. + /// + /// - Returns: [DataSymbolSupportedDataType] + func evaluate() -> [DataSymbolSupportedDataType]? +} + +extension GraphSymbol { + + /// Generate a `ComputationGraph` object according to `inBounds` and `outBounds` information of this symbol. + /// The generated graph ending with this symbol. + /// + /// - Returns: A `ComputationGraph` object. + public func generateOutputGraph(_ label: String? = nil) -> Graph { + var graphLabel = label + if graphLabel == nil { graphLabel = "ComputationGraph(Generate from symbol \(self.symbolLabel)[\(self.UID)])" } + + var symbolQueue = [GraphSymbol]() + symbolQueue.append(self) + + let graph = ComputationGraph(graphLabel!) + while !symbolQueue.isEmpty { + // pop first + let symbol = symbolQueue.first! + symbolQueue.remove(at: 0) + + graph.addSymbols(symbol) + symbolQueue.append(contentsOf: symbol.inBounds) + } + + return graph + } +} + +public protocol DataSymbol: GraphSymbol { + /// Data source + var dataSource: SymbolDataSource {get set} + + /// Indicate if grads of this data symbol should be used to update itself. + /// + /// ## updatable of DataSymbol V.S. enabledParameterUpdate of OperatorSymbol + /// Both attribute controls data updating in backward training. + /// `updatable` of a data symbol controls whether updating this symbol's binded data, + /// while `enabledParameterUpdate` controls whether updating operator symbol's inbounds data symbols. + /// If `enabledParameterUpdate` is `false`, Serrano will ignore `updatable` attributes of + /// all data symbols in this operator symbol's inbounds. + /// So user can think `enabledParameterUpdate` has high priority over `updatable` on controlling. + var updatable: Bool {get set} + + /// This attribute hold the grad of this data symbol in this epoch training. + /// This grad value is chained value (i.e. grad of final output of the grpah against this data) + /// + /// - Note: `currentGrad` will be added to `historyGrads` at the end of each backward training. + var currentGrad: DataSymbolSupportedDataType? {get set} + + /// Binded data + var bindedData: DataSymbolSupportedDataType? {get set} + + /// Bind data to data symbol + /// + /// - Parameter data: data to bind + /// - Returns: if binded successfully + func bindData(_ data:DataSymbolSupportedDataType) -> Bool + + /// This function used to update a data symbol's value and grad during backward. + /// + /// - Parameters: + /// - optimizer: optimizer + /// - gradValue: this epoch calculated value + /// - upGrads: up grads from backward chain + mutating func backwardUpdate(_ optimizer: Optimizer, gradValue: DataSymbolSupportedDataType, upGrads:[DataSymbolSupportedDataType]) +} + +extension DataSymbol { + + /// This function used to update a data symbol's value during backward. + /// + /// 1. Calculate chained grads + /// 2. Use optimizer updaing bined value + /// 3. Store this epoch's grad + /// + /// - Parameters: + /// - optimizer: optimizer + /// - gradValue: this epoch calculated value + /// - upGrads: up grads from backward chain + public mutating func backwardUpdate(_ optimizer: Optimizer, gradValue: DataSymbolSupportedDataType, upGrads:[DataSymbolSupportedDataType]) { + // chained grads + if self.symbolType == SymbolType.Scalar { + // Oprator takes in tensors and outputs tensors + // and I cannot think of any operator where a scalar parameter is trainable. + // So for scalar symbol, I doubt if we needs to calcualte its grads.... + // FIXME: If you think it is not neccessary. + let gradValueFloat = (gradValue as! SupportedScalarDataType).floatValue + var gradValue:Float = 0.0 + for upGrad in upGrads { + if upGrad is Tensor { + // tensor up grad, do reduce sum + let upGradTensor = upGrad as! Tensor + let gradTensor = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: [1])) + let reduceOp = ReduceSumOperator(inputTensors: [upGradTensor], outputTensors: [gradTensor], + axis:Array(0.. [DataSymbolSupportedDataType]?` +extension ScalarSymbol { + public func evaluate() -> [DataSymbolSupportedDataType]? { + if bindedData == nil { + return nil + } else { + return [self.bindedData as! DataSymbolSupportedDataType] + } + } +} + +/** +Symbol represents a tensor object +*/ +public protocol TensorSymbol: DataSymbol { + /// Shpe of this tensor symbol + var shape: TensorShape {get set} +} + +/// Default implementation of `evaluate() -> Tensor` +extension TensorSymbol { + public func evaluate(_ mode: OperatorComputationMode = .GPU) -> [DataSymbolSupportedDataType]? { + let graph = self.generateOutputGraph() + let result = graph.forward(mode: mode) + if result == nil { + return nil + } else { + return result! as [DataSymbolSupportedDataType] + } + } +} + +/** +Symbol represent an operator calcualtion +*/ +public protocol OperatorSymbol: GraphSymbol { + /// Input symbols for this operator + /// + /// - Note: symbols in this attribute should also be included in `inBounds` + var inputSymbols: [TensorSymbol] {get set} + + /// Input shapes tensor symbols in `inputSymbols` + var inputTensorShapes: [TensorShape] {get set} + + /// Parameters (weights, bias etc.) symbols for this operator. + /// For operator having no params, this should be an empty array. + /// + /// - Note: symbols in this attribute should also be included in `inBounds` + var paramSymbols: [DataSymbol] {get set} + + /// The operator instance + var serranoOperator: ComputableOperator {get set} + + /// Control if update asscoiated operator's parameter + /// + /// ## updatable of DataSymbol V.S. enabledParameterUpdate of OperatorSymbol + /// Both attribute controls data updating in backward training. + /// `updatable` of a data symbol controls whether updating this symbol's binded data, + /// while `enabledParameterUpdate` controls whether updating operator symbol's inbounds data symbols. + /// If `enabledParameterUpdate` is `false`, Serrano will ignore `updatable` attributes of + /// all data symbols in this operator symbol's inbounds. + /// So user can think `enabledParameterUpdate` has high priority over `updatable` on controlling. + var enabledParameterUpdate: Bool {get set} + + /// Get output symbols of this operator + /// + /// - Returns: Array of SerranoTensorSymbol. + func outputSymbols() -> [TensorSymbol] + + /// Add to `paramSymbols` + /// + /// - Parameter symbol: new symbol + func addToParamSymbols(_ symbol: GraphSymbol) + + /// Get grads on any inbounds symbol + /// + /// - Parameter inputSymbol: + /// - Returns: SupportedGradsDataType + func gradsOnInput(_ inputSymbol: DataSymbol) -> DataSymbolSupportedDataType + + /// Get corresponding inbound symbol for a label following rule in `Operator` method + /// `gradCompute(_:)`. + /// + /// - Parameter label: label + /// - Returns: DataSymbol + func inboundSymbolForGradLabel(_ label: String) -> DataSymbol? + + /// For a given `symbol` in inbounds, return a list of output symbols that + /// it involves with calculation. + /// + /// - Parameter symbol: target symbol + /// - Returns: list of output symbol + func gradRelatedOutputSymbols(onInboundSymbol symbol: DataSymbol) -> [DataSymbol] +} + +/// Default implementation of `evaluate() -> [Tensor]` +extension OperatorSymbol { + public func evaluate(_ mode: OperatorComputationMode = .GPU) -> [DataSymbolSupportedDataType]? { + let graph = self.generateOutputGraph() + return graph.forward(mode: mode) + } + + /// Add to `paramSymbols` + /// + /// - Parameter symbol: new symbol + public func addToParamSymbols(_ symbol: GraphSymbol) { + if !self.paramSymbols.contains { (graphSymbol) -> Bool in + return graphSymbol.UID == symbol.UID + } { + var o = self as OperatorSymbol + guard symbol is DataSymbol else { + SerranoLogging.errorLogging(message: "Symbol argument is not a DataSymbol type.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Fatalerror raised by Serrano. Check log for details") + } + o.paramSymbols.append(symbol as! DataSymbol) + } + } + + /// Get grads on any inbounds symbol. + /// Call attached operator's `gradCompute(_:, :)` to get grads. + /// Use return tensor's label to identify corresponding input datasymbol. + /// + /// ## Identify corresponding input + /// The tensor label of returned tensors could be used to identify its correspoding input + /// following below rules: + /// - __Input tensor__. `input_{i}` where `i` is the corresponding input tensor's index in `inputTensors` + /// - __Parameter__. The parameter's name. + /// + /// - Note: If `inputSymbol` does not belong to this operator symbol. + /// `fatalError()` raised. + /// + /// - Parameter inputSymbol: + /// - Returns: SupportedGradsDataType + public func gradsOnInput(_ inputSymbol: DataSymbol) -> DataSymbolSupportedDataType { + // check belonging + guard (self.inBounds.contains {$0.UID == inputSymbol.UID}) else { + SerranoLogging.errorLogging(message: "Symbol argument is not a DataSymbol type.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Fatalerror raised by Serrano. Check log for details") + } + + let label:String + if (self.inputSymbols.contains {inputSymbol.UID == $0.UID}) { + label = "input_\(self.inputSymbols.index {$0.UID == inputSymbol.UID}!)" + } else { + // We just use symbol's label her cause param symbol is generated by operator's paramSymbols() function. + // This function will assgin proper labels. + label = inputSymbol.symbolLabel + } + + let grad = self.serranoOperator.gradCompute(.GPU).filter { $0.key == label}.first?.value + guard grad != nil else { + SerranoLogging.errorLogging(message: "Unexpected error. Could not find grads for target input.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Fatalerror raised by Serrano. Check log for details") + } + return grad! + } + + /// Get inbound data symbol for target label. + /// + /// ## Identify corresponding input + /// The returned label of data could be used to identify its correspoding input + /// following below rules: + /// - __Input tensor__. `input_{i}` where `i` is the corresponding input tensor's index in `inputTensors` + /// - __Parameter__. The parameter's name. + /// + /// - Note: may return `nil` + /// + /// - Parameter label: label + /// - Returns: symbol + public func inboundSymbolForGradLabel(_ label: String) -> DataSymbol? { + let symbol: DataSymbol? + if label.contains("input") { + // input tensor symbol + symbol = self.inputSymbols[Int(label.split(separator: "_").last!)!] + } else { + // parameter data symbol + symbol = self.paramSymbols.filter {$0.symbolLabel == label}.first + } + return symbol + } + + /// For a given `symbol` in inbounds, return a list of output symbols that + /// it involves with calculation. + /// + /// - Parameter symbol: target symbol + /// - Returns: list of output symbol + public func gradRelatedOutputSymbols(onInboundSymbol symbol: DataSymbol) -> [DataSymbol] { + var symbols = [DataSymbol]() + if self.serranoOperator.mapType == OperatorMappingType.Constant { + symbols.append(self.outBounds[0] as! DataSymbol) + } else { + let index = self.inputSymbols.index(where: {$0.UID == symbol.UID}) + if index != nil { + // input symbol + symbols.append(self.outBounds[index!] as! DataSymbol) + } else { + // param symbols, all output + symbols.append(contentsOf: self.outBounds[index!] as! [DataSymbol]) + } + } + return symbols + } +} + diff --git a/Source/Serrano/core/tensor.swift b/Source/Serrano/core/tensor.swift new file mode 100644 index 0000000..7f989b4 --- /dev/null +++ b/Source/Serrano/core/tensor.swift @@ -0,0 +1,1770 @@ +// +// tensor.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/15/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Accelerate + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +/** + Defined the supported data type stored in Tensor object. + + Should be matching with supported scalar data types in + [Apple's Metal specification](https://developer.apple.com/metal/metal-shading-language-specification.pdf) (section 2.1) + + Currently compatible with v1.2 with types: + + - int32: A signed two’s complement 32-bit integer + - uint32: An unsigned 32-bit integer + - float16: A 16-bit floating-point + - float32: A 32-bit floating-point + */ +public enum TensorDataType { + ///A signed two’s complement 32-bit integer + case int + case float + case double +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +public class TensorUtils { + /** + Randomlly generate a `Tensor` object. + */ + public static func generateRandomTensor(targetTensorShape shape: TensorShape, maxVal: Float) -> Tensor{ + var array = [SupportedScalarDataType]() + switch shape.dataType { + case .double: + for _ in 0.. [Float] { + var flatten = [Float]() + for element in array { + if element is SupportedNestedType { + flatten.append(contentsOf: TensorUtils.flattenArray(array: element as! [Any], dataType: type)) + } else if element is SupportedScalarDataType { + switch type { + case .int: + flatten.append(Float(element as! Int)) + case .float: + flatten.append(element as! Float) + case .double: + flatten.append(Float(exactly: element as! Double)!) + } + } else { + SerranoLogging.warningLogging(message: "Meet unsupported datatype: \(type(of: element))", file: "\(#file)", function: "\(#function)", line: "\(#line)") + } + } + + return flatten + } +} + + +infix operator .==: ComparisonPrecedence + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: + +/** + The tensor shape description. Specify the shape and data type of a `Tensor` data object. + + ## dataType + + + ## Shape + The `shape` attrribute defines the dimension of a `Tensor` object. In `serrano`, we follow `row-marjor` + order to store and access elements in a `Tensor` object and each __row__ is represented as an array. + For a given shape array with `n` indices `[i_0, i_1, ..., i_(n-1)]`, each index from `i_0` to `i_(n-2)` defines the number of rows in its + previous dimension. The last index define the number of elements in its previous dimention. + For example, a `TensorShape` object with `shpae` as `[2, 1, 3]`. + It's 1st dimension has `2` rows in which each row has `1` row with 3 elements. + + + User should be clear and unserstanding what a `Tensor` object _looks_ like when they passing in a `TensorShape` argument. + For example, a `Tensor` object with shape `[2, 3]`, it can be visulized as a nested array like below: + ``` + // shape [2, 3] + [ + [1.0, 0.5, 1.3], + [2.0, 4.2, 6.7], + ] + ``` + And a typical real-world example, a 3-channel RGB image data could be represented with shape `[3, image_hight, image_width]`: + ``` + [ + // R channel frame + [ + [232, ..., 123], // (# of Int elements) = image_width + . + . + . + [113, ..., 225] + ], // (# of Array elements) = image_hight + + // G channel frame + [ + [232, ..., 123], + . + . + . + [113, ..., 225] + ], + + // B channel frame + [ + [232, ..., 123], + . + . + . + [113, ..., 225] + ] + ] + ``` + +## Equatable +Two TensorShape objects are __equal__ (`==`)if they have the same `shape`. + +Two TensorShape objects are __dot equal__ (`.==`) if they have the same `shape` and same `dataType`. + +## Rank is `0` +If a `Tensor` object's shapeArray has `0` rank, it indicates that it just contains a __scalar__ value. + */ +public struct TensorShape: Equatable, Comparable { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Properties + + /// Data type + var dataType:TensorDataType + + /// Shape array + var shapeArray: [Int] + + /// Rank of dimensions + var rank: Int { + get { + return self.shapeArray.count + } + } + + /// Element count + var count: Int { + get { + return self.shapeArray.reduce(1, *) + } + } + + /// Description + var description: String { + get { + return "TensorShape(rank: \(self.rank), shape: \(self.shapeArray), dataType:\(self.dataType))" + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + + /// Public init for out init + /// + /// - Parameters: + /// - dataType: dataType + /// - shape: shape + public init(dataType: TensorDataType, shape: [Int]) { + self.dataType = dataType + self.shapeArray = shape + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Check if `shape` array is valid + func shapeVerify() -> Bool { + if self.shapeArray.count == 0 { + return false + } + + for dimensionSize in self.shapeArray { + if dimensionSize <= 0 { + return false + } + } + return true + } + + /// Get `shape` array in reversed + func reversedShapArray() -> [Int] { + return Array(self.shapeArray.reversed()) + } + + /// Get transposed shape of current shape. + /// - Note: only works for shapes with rank values as `2`. Otherwise, `fatalError` will be throws. + func transposed() -> TensorShape { + guard self.rank == 2 else { + SerranoLogging.errorLogging(message: "Trying to get transposed shape from shape with rank \(self.rank)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + return TensorShape(dataType: self.dataType, shape: [self.shapeArray[1], self.shapeArray[0]]) + } + + + /// Tow shapes dot equals when have same `dataType` and `==`. + /// + /// - Parameters: + /// - shapeA: shape A + /// - shapeB: shape B + public static func .==(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + return shapeA.dataType == shapeB.dataType && shapeA == shapeB + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Equable protocol + + /// Two shap equals when has same rank and same dimensions. + /// + /// - Parameters: + /// - shapeA: shapeA description + /// - shapeB: shapeB description + /// - Returns: return value description + public static func ==(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + return shapeA.rank == shapeB.rank && shapeA.shapeArray.elementsEqual(shapeB.shapeArray) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Comparable protocol + + + /// `shapeA` larger than `shapeB` if: + /// - `shapeA` has a larger `rank` or + /// - `shapeA` and `shapeB` has same rank while `shapeA` has a larger element `count` + /// + /// - Parameters: + /// - shapeA: shapeA description + /// - shapeB: shapeB description + /// - Returns: return value description + public static func >(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + if shapeA.rank > shapeB.rank { + return true + } else if shapeA.rank == shapeB.rank { + return shapeA.count > shapeB.count + } else { + return false + } + } + + /// `shapeA` larger than or equal to `shapeB` if: + /// - `shapeA` has larger `rank` or + /// - `shapeA` and `shapeB` has same rank while `shapeA` has a larger or same`count` + /// + /// - Parameters: + /// - shapeA: shapeA description + /// - shapeB: shapeB description + /// - Returns: return value description + public static func >=(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + if shapeA.rank > shapeB.rank { + return true + } else if shapeA.rank == shapeB.rank { + return shapeA.count >= shapeB.count + } else { + return false + } + } + + /// `shapeA` less than `shapeB` if: + /// - `shapeA` has smaller `rank` or + /// - `shapeA` and `shapeB` has same rank while `shapeA` has a smaller `count` + /// + /// - Parameters: + /// - shapeA: shapeA description + /// - shapeB: shapeB description + /// - Returns: return value description + public static func <(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + if shapeA.rank < shapeB.rank { + return true + } else if shapeA.rank == shapeB.rank { + return shapeA.count < shapeB.count + } else { + return false + } + } + + /// `shapeA` less than or equal to `shapeB` if: + /// - `shapeA` has smaller `rank` or + /// - `shapeA` and `shapeB` has same rank while `shapeA` has a smaller or same `count` + /// + /// - Parameters: + /// - shapeA: shapeA description + /// - shapeB: shapeB description + /// - Returns: return value description + public static func <=(shapeA: TensorShape, shapeB: TensorShape) -> Bool { + if shapeA.rank < shapeB.rank { + return true + } else if shapeA.rank == shapeB.rank { + return shapeA.count <= shapeB.count + } else { + return false + } + } + +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// MARK: Operators + +/// Custom Operator: in-place addition of two tensors or tensor-scalar, result stored in left tensor. +infix operator &+: AdditionPrecedence + +/// Custom Operator: in-place substraction of two tensors or tensor-scalar, result stored in left tensor. +infix operator &-: AdditionPrecedence + +/// Custom Operator: in-place multiplication of two tensors or tensor-scalar, result stored in left tensor. +infix operator &*: MultiplicationPrecedence + +/// Custom Operator: in-place division of two tensors or tensor-scalar, result stored in left tensor. +infix operator &/: MultiplicationPrecedence + + + +/// Custom Operator: broadcast addition between two tensors. +infix operator .+: AdditionPrecedence + +/// Custom Operator: broadcast substraction between two tensors. +infix operator .-: AdditionPrecedence + +/// Custom Operator: broadcast multiplication between two tensors. +infix operator .*: MultiplicationPrecedence + +/// Custom Operator: broadcast division between two tensors. +infix operator ./: MultiplicationPrecedence + + + +/// Custom Operator: in-place broadcast addition of two tensors or tensor-scalar, result stored in left tensor. +infix operator .&+: AdditionPrecedence + +/// Custom Operator: in-place broadcast substraction of two tensors or tensor-scalar, result stored in left tensor. +infix operator .&-: AdditionPrecedence + +/// Custom Operator: in-place broadcast multiplication of two tensors or tensor-scalar, result stored in left tensor. +infix operator .&*: MultiplicationPrecedence + +/// Custom Operator: in-place broadcast division of two tensors or tensor-scalar, result stored in left tensor. +infix operator .&/: MultiplicationPrecedence + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//MARK: + +/** +A `Tensor` object is a n-dimension page-aligned data container with fixed-size memory space. +The attribute `shape` specifying the dimension information of a `Tensor` object. + +## All elements stored as `Float` values +No matter what type of array user passing in: `Double` or `Float` or `Int`, inside a `Tensor` object +values will be converted and stored as `Float` (32-bit signle precision). +`Serrano` chose this because 32-bit `Float` is the maximum precise floating data type `Metal` could support (v1.2). + + +## Inside Memory Layout +Inside a `Tensor` object, it maintains a _virtual_ 1-d array as a contiguous memory space manually allocated. +Elements are stored following `row-major` order. Details can be found in `TensorShape` docs. + + +## Tensor-Tensor arithmetic operators +Besides [Operators]() which contains many math and NN operations, `Tensor` object itself implements/overloads common +arithmetic operators. +Tensor object support element-wise arithmetic operation with or without broadcasting. +Also it supports in-place operation choice. + +### Element-wise operation without broadcasting: +- `+` Addition without broadcasting +- `-` Substraction without broadcasting +- `*` Multiplication without broadcasting +- `/` Division without broadcasting + +### Element-wise in-place operation: +- `&+` Addition without broadcasting +- `&-` Substraction without broadcasting +- `&*` Multiplication without broadcasting +- `&/` Division without broadcasting + +### Element-wise operation with broadcasting: +- `.+` Addition without broadcasting +- `.-` Substraction without broadcasting +- `.*` Multiplication without broadcasting +- `./` Division without broadcasting + +### Element-wise in-place operation with broadcasting: +- `.&+` Addition without broadcasting +- `.&-` Substraction without broadcasting +- `.&*` Multiplication without broadcasting +- `.&/` Division without broadcasting + +Example usage: +```swift +/// Element wise addition without broadcasting +let tensorA = Tensor(repeatingValue: 1.0, + tensorShape: TensorShape(dataType:.float, shape: [2, 5])) +let tensorB = Tensor(repeatingValue: 2.0, + tensorShape: TensorShape(dataType:.float, shape: [2, 5])) +let result1 = tensorA + tensorB + +/// Element wise in-place addition without broadcasting +let result2 = tensorA &+ tensorB +print(result2 == tensorA) // true + +/// Element wise addition with broadcasting +let tensorD = Tensor(repeatingValue: 2.0, + tensorShape: TensorShape(dataType:.float, shape: [1, 5])) +let result3 = tensorA .+ tensorD + +/// Element wise in-place addition with broadcasting +let resunt4 = tensorA .&+ tensorD +print(result4 == tensorA) // true +``` + +## Directly used as TensorSymbol +`Tensor` conforms to `TensorSymbol` protocol. +So a tensor object could be used as a symbol participating in graph computation. + + */ +public class Tensor: Hashable, Equatable, TensorSymbol { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Properties + + /// shape + internal var _shape: TensorShape + + /// Allocated aligned memory size in bytes + internal var _allocatedSize: Int + + /// The base address of allocated memory. + internal var _dataMemoryBaseAdrress: UnsafeMutablePointer + + /// Convenience `Float` reader + internal var _elementReader: UnsafeMutableBufferPointer + + /// How many elements can store + internal var _capacity: Int + + /// Slice marker. + /// Indicate if this tensor object is a slice from another tensor. + /// If `true`, this object is just a reference slice and should not own the memeory. + internal var _sliceMarker: Bool = false + + /// The root tensor of a sliced tensor + internal var _sliceRootTensor: Tensor? = nil + + /// The parent tensor of a sliced tensor + internal var _sliceParentTensor: Tensor? = nil + + /// The index of this slice object in raw tensor. + /// `nil` if not a sliced tensor + internal var _sliceIndex: Int? + + /// Count of data elements tored + public var count: Int { + get { + return self._shape.shapeArray.reduce(1, *) + } + } + + + /// Dimension of the array + public var dimension: Int { + get { + return self._shape.shapeArray.count + } + } + + /// How many elements can store + public var capacity: Int { + get { + return self._capacity + } + } + + + /// Shape + public var shape: TensorShape { + get { + return self._shape + } + set(newShape) { + self._shape = newShape + } + } + + /// Allocated memeory bytes + public var allocatedBytes: Int { + get { + return self._allocatedSize + } + } + + /// Readable description of a object + public var description: String { + get { + return "TensorObject at \(self._dataMemoryBaseAdrress), Allocated bytes: \(self._allocatedSize), Element count: \(self._shape.shapeArray.reduce(1, *)), shape: \(self._shape.shapeArray)" + } + } + + /// A readable label for distinguishing different tensors. Serrano does not check label unique. + public var label: String = "Tensor" + + /// Base address of allocated memeory space + /// + /// - Warning: + /// User could reuse a `Tensor` object with this pointer by assigning new values. + /// However, it must be very careful with the boundary. + public var contentsAddress: UnsafeMutablePointer { + get { + return self._dataMemoryBaseAdrress + } + } + + /// Float value reader pointer + public var floatValueReader: UnsafeMutableBufferPointer { + get { + return self._elementReader + } + } + + /// Rank + public var rank: Int { + get { + return self._shape.shapeArray.count + } + } + + /// Slice marker. + /// Indicate if this tensor object is a slice from another tensor. + /// If `true`, this object is just a reference slice and should not own the memeory. + public var isSliceTensor: Bool { + get { + return self._sliceMarker + } + } + + /// The root tensor of a sliced tensor + /// - Note: `nil` if `_sliceMarker` is `false` + public var sliceRootTensor: Tensor? { + get { + return self._sliceRootTensor + } + } + + + /// The parent tensor of a sliced tensor + /// - Note: `nil` if `_sliceMarker` is `false` + public var sliceParentTensor: Tensor? { + get { + return self._sliceParentTensor + } + } + + /// The index of this slice object in raw tensor. + /// `nil` if not a sliced tensor + public var sliceIndex: Int? { + get { + return self._sliceIndex + } + } + + /// Conforms to protocol `TensorSymbol`. + public var symbolType: SymbolType = SymbolType.Tensor + + /// Conforms to protocol `TensorSymbol`. + public var UID: String = "" + + /// Conforms to protocol `TensorSymbol`. + public var symbolLabel: String = "" + + /// Inbound symbols list. Conforms to `GraphSymbol`. + /// To prevent from cycle reference, we dynamic constructing this attribute from `inBoundsWeak`. + public var inBounds: [GraphSymbol] { + get { + return self.inBoundsWeak.filter {$0.value != nil}.map {$0.value!} + } + set(bounds) { + for symbol in bounds { + self.addToInBound(symbol) + } + } + } + + /// Outbound symbols list. Conforms to `GraphSymbol`. + /// To prevent from cycle reference, we dynamic constructing this attribute from `inBoundsWeak`. + public var outBounds: [GraphSymbol] { + get { + return self.outBoundsWeak.filter {$0.value != nil}.map {$0.value!} + } + set(bounds) { + for symbol in bounds { + self.addToOutBound(symbol) + } + } + } + + /// Weak reference array of inbounds objects + internal var inBoundsWeak: [WeakSerranoGraphSymbol] = [WeakSerranoGraphSymbol]() + + /// Weak reference array of outbounds objects + internal var outBoundsWeak: [WeakSerranoGraphSymbol] = [WeakSerranoGraphSymbol]() + + /// __Read-only__. Conforms to protocol `TensorSymbol`. + /// + /// - Note: If a tensor object used as a `TensorSymbol`, atrribute `bindedData` could not be modifed. + public var bindedData: DataSymbolSupportedDataType? { + get { + return self + } + set { + SerranoLogging.errorLogging(message: "Could not modify the bindedData of a TensorSymbol represented from a tensor object.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + + /// Conforms to protocol `TensorSymbol`. + public var dataSource: SymbolDataSource = SymbolDataSource.Other + + /// If differentiable + public var updatable = false + + /// Current grad + public var currentGrad: DataSymbolSupportedDataType? + + /// If enabled history grads recording. + /// Default is `false`. + public var historyGradsEnabled = false + + /// history grads + public var historyGrads: [DataSymbolSupportedDataType] = [Tensor]() + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - subscript + + + /// Check if index is valid for fetching a single element + /// + /// - Parameter index: index description + /// - Returns: return value description + public func indexIsValid(_ index: [Int]) -> Bool { + if index.count != self._shape.shapeArray.count { + return false + } else { + for dimension in 0..= self._shape.shapeArray[dimension] || index[dimension] < 0 { + return false + } + } + return true + } + } + + + + /// Get offset in terms of element counting from valid index + /// + /// - Parameter index: valid index list + /// - Returns: offset + internal func offSetFromIndex(_ index: [Int]) -> Int { + var offset = 0 + for i in 0..<(index.count-1) { + offset += index[i] * self._shape.shapeArray.suffix(from: i+1).reduce(1, *) + } + offset += index.last! + return offset + } + + /// Custom subscript to fetch single `Float` element + /// + /// - Parameter index: Indices list + subscript(_ index: Int...) -> Float { + set(newValue) { + guard indexIsValid(index) else { + fatalError("[serrano]Invalid index \(index) for tensor with shape \(self._shape.shapeArray)") + } + let offset = self.offSetFromIndex(index) + self._elementReader[offset] = newValue + } + + get { + guard indexIsValid(index) else { + fatalError("[serrano]Invalid index \(index) for tensor with shape \(self._shape.shapeArray)") + } + let offset = self.offSetFromIndex(index) + return (self._dataMemoryBaseAdrress + offset).pointee + } + } + + /// Custom subscript to fetch single `Float` element + /// + /// - Parameter index: Indices list + subscript(_ index: [Int]) -> Float { + set(newValue) { + guard indexIsValid(index) else { + fatalError("[serrano]Invalid index \(index) for tensor with shape \(self._shape.shapeArray)") + } + let offset = self.offSetFromIndex(index) + self._elementReader[offset] = newValue + } + + get { + guard indexIsValid(index) else { + fatalError("[serrano]Invalid index \(index) for tensor with shape \(self._shape.shapeArray)") + } + let offset = self.offSetFromIndex(index) + return (self._dataMemoryBaseAdrress + offset).pointee + } + } + + + /// Internal use, already checked boundary before fetch or set + /// + /// - Warning: May cause unexpected result or fatal error if `index` is not valid. + /// + /// - Parameter index: index array + public subscript(withoutChecking index:[Int]) -> Float { + set(newValue) { + let offset = self.offSetFromIndex(index) + self._elementReader[offset] = newValue + } + + get { + let offset = self.offSetFromIndex(index) + return (self._dataMemoryBaseAdrress + offset).pointee + } + } + + + /// Get element value from `index`. + /// If input index is invalid, return `missingValue`. + /// + /// - Parameters: + /// - index: index array + /// - missingValue: default value for missing elements. Default is `0.0` + /// - Returns: value + public func fetchValueOrDefault(_ index: [Int], missingValue: Float = 0.0) -> Float { + if indexIsValid(index) { + return self[withoutChecking:index] + } else { + return missingValue + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + + /// Designated init. + /// + /// - Note: Usually, user should not call this init directly. + /// + /// - Parameters: + /// - shape: shape + /// - allocateSize: allocateSize + /// - capacity: capacity + /// - dataMemoryBaseAddres: dataMemoryBaseAddres + /// - elementReader: elementReader + public init(shape: TensorShape, allocateSize: Int, capacity: Int, + dataMemoryBaseAddres: UnsafeMutablePointer, elementReader:UnsafeMutableBufferPointer) { + self._shape = shape + self._allocatedSize = allocateSize + self._capacity = capacity + self._dataMemoryBaseAdrress = dataMemoryBaseAddres + self._elementReader = elementReader + } + + /// Initial a tensor with repeating value + /// + /// - Parameters: + /// - repeatingValue: repeat value + /// - shape: shape + public convenience init(repeatingValue value: Float, tensorShape shape: TensorShape) { + let pageSize: Int = Int(getpagesize()) + let needSize = shape.count * MemoryLayout.stride + let allocateSize = needSize.padded(alignmentSize: pageSize)! + let capacity = allocateSize / MemoryLayout.stride + var memory: UnsafeMutableRawPointer? = nil + posix_memalign(&memory, pageSize, allocateSize) // use posix_memalign to make it work on MAC + let dataMemoryBaseAddress = UnsafeMutablePointer(OpaquePointer(memory!)) + let elementReader = UnsafeMutableBufferPointer(start: dataMemoryBaseAddress, count: capacity) + + self.init(shape: shape, + allocateSize: allocateSize, + capacity: capacity, + dataMemoryBaseAddres: dataMemoryBaseAddress, + elementReader: elementReader) + + // initial + self._dataMemoryBaseAdrress.initialize(to: value, count: capacity) + } + + /// Construct a tensor from a flatten (1-D) swift array. + /// + /// - Parameters: + /// - array: 1-D array. + /// - shape: The shape which the constructed tensor have. The count of this shape should be the same of count of passed in array. + public convenience init(fromFlatArray array:[SupportedScalarDataType], tensorShape shape: TensorShape) { + self.init(repeatingValue: 0.0, tensorShape: shape) + + // Initialize values + // Note: initialize(from:count:) not used here cause we can not make sure passed in array is contiguous. + for i in 0.., + count: Int, shape: TensorShape, index: Int) { + self._dataMemoryBaseAdrress = sliceContentAddress + self._elementReader = UnsafeMutableBufferPointer(start: self._dataMemoryBaseAdrress, count: count) + self._shape = shape + self._capacity = count + self._sliceMarker = true + self._sliceParentTensor = parentTensor + self._sliceIndex = index + self._allocatedSize = count * MemoryLayout.stride + // setup root + if parentTensor.isSliceTensor { + // parent is a sliced tensor + self._sliceRootTensor = parentTensor.sliceRootTensor! + } else { + // parent is a normal tensor + self._sliceRootTensor = parentTensor + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Deinitializers + + /// Need to free allcoated memory space manually. + deinit { + if !self._sliceMarker { // Slice tensor could not free memeory + free(self._dataMemoryBaseAdrress) + + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Util methods + + /// Reload data from a flat array. + /// + /// - Note: If `array` size < tensor's `count` + /// + /// - Parameter array: + public func reloadData(fromFlatArray array:[SupportedScalarDataType], tensorShape shape: TensorShape) { + guard self.capacity >= shape.count else { + SerranoLogging.errorLogging(message: "Trying to load a data array larer than tensor's capacity size. Capacity \(self.capacity), shape count \(shape.count), array count: \(array.count)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + for i in 0.. [Float] { + var faltArray = [Float]() + for offset in 0.. [Any] { + if self.rank == 0 { + return [self.floatValueReader[0]] + } else { + return constructNestedArrayFrom(location: []) + } + } + + + /// Recursively construct nested array + /// + /// - Parameter shape: shape needs to construct + /// - Returns: result + internal func constructNestedArrayFrom(location: [Int]) -> [Any] { + let currDimSize = self._shape.shapeArray[location.count] + var array = [Any]() + + if location.count == self._shape.shapeArray.count - 1 { + var offset = 0 + for i in 0.. Bool { + return lhs._dataMemoryBaseAdrress == rhs._dataMemoryBaseAdrress + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor manipulation + + /// Return a new tensor contains the absolute values of this tensor. + /// Same effect as using `AbsOperator`. + /// + /// - Returns: new tensor object with absed values. + public func abs() -> Tensor { + let newTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(self.shape) + let absOp = AbsOperator(inputTensors: [self], outputTensors: [newTensor]) + absOp.compute() + return newTensor + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Tensor arithmetic operations (not support broadcasting) + + + /// Element-wise addition. __Not support braodcasting__. + /// Same effect as using `AddOperator`. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func +(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = AddOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise substraction. __Not support braodcasting__. + /// Same effect as using `SubOperator`. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func -(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = SubOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise multiplication. __Not support braodcasting__. + /// Same effect as using `AddOperator`. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func *(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = MultOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise division. __Not support braodcasting__. + /// Same effect as using `DivOperator`. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func /(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = DivOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Tensor in-place arithmetic operations (not support broadcasting) + + /// Element-wise in-place addition. __Not support braodcasting__. + /// Same effect as using `AddOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calculated. + @discardableResult public static func &+(left: Tensor, right:Tensor) -> Tensor { + let op = AddOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place substraction. __Not support braodcasting__. + /// Same effect as using `SubOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calculated. + @discardableResult public static func &-(left: Tensor, right:Tensor) -> Tensor { + let op = SubOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place multiplication. __Not support braodcasting__. + /// Same effect as using `MultOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calculated. + @discardableResult public static func &*(left: Tensor, right:Tensor) -> Tensor { + let op = MultOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place division. __Not support braodcasting__. + /// Same effect as using `DivOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape, program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calculated. + @discardableResult public static func &/(left: Tensor, right:Tensor) -> Tensor { + let op = DivOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Tensor arithmetic operations (support broadcasting) + + /// Element-wise addition. __support braodcasting__. + /// Same effect as using `BrodcastAddOperator`. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func .+(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = BroadcastAddOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise substraction. __support braodcasting__. + /// Same effect as using `BroadcastSubOperator`. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func .-(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = BroadcastSubOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise multiplication. __support braodcasting__. + /// Same effect as using `BroadcastMultOperator`. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor + public static func .*(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = BroadcastMultOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + /// Element-wise division. __support braodcasting__. + /// Same effect as using `BroadcastDivOperator`. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: result tensor, a new created tensor. + public static func ./(left: Tensor, right:Tensor) -> Tensor { + let outputTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let op = BroadcastDivOperator(inputTensors: [left, right], outputTensors: [outputTensor]) + op.compute() + return outputTensor + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Tensor in-place arithmetic operations (support broadcasting) + + /// Element-wise in-place addition. __support braodcasting__. + /// Same effect as using `BrodcastAddOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calcualted + @discardableResult public static func .&+(left: Tensor, right:Tensor) -> Tensor { + let op = BroadcastAddOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place substraction. __support braodcasting__. + /// Same effect as using `BroadcastSubOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calcualted + @discardableResult public static func .&-(left: Tensor, right:Tensor) -> Tensor { + let op = BroadcastSubOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place multiplication. __support braodcasting__. + /// Same effect as using `BroadcastMultOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calcualted + @discardableResult public static func .&*(left: Tensor, right:Tensor) -> Tensor { + let op = BroadcastMultOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + /// Element-wise in-place division. __support braodcasting__. + /// Same effect as using `BroadcastDivOperator`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Warning: If two tensors don't have same shape or one of them cannot be broadcasted to anoter one, + /// program would be aborted (`fatalError()` called). + /// + /// - Parameters: + /// - left: left tensor + /// - right: right tensor + /// - Returns: left tensor after calcualted + @discardableResult public static func .&/(left: Tensor, right:Tensor) -> Tensor { + let op = BroadcastDivOperator(inputTensors: [left, right], outputTensors: [left]) + op.compute() + return left + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Scalar arithmetic operations. + + /// A tensor plus a scalar variable. Ex. `[2, 3] + 0.5 --> [2.5, 3.5]`. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: Result tensor, new created. + public static func + (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let outAddress = tensor.contentsAddress + let inAddress = left.contentsAddress + var scalar = rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + /// A tensor substract a scalar variable. Ex. `[2, 3] - 0.5 --> [1.5, 2.5]`. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: Result tensor, new created. + public static func - (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let outAddress = tensor.contentsAddress + let inAddress = left.contentsAddress + var scalar = -rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + /// A tensor multiply a scalar variable. Ex. `[2, 3] * 0.5 --> [1.0, 1.5]`. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: Result tensor, new created. + public static func * (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let outAddress = tensor.contentsAddress + let inAddress = left.contentsAddress + var scalar:Float = rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsmul(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + /// A tensor divide a scalar variable. Ex. `[2, 3] / 0.5 --> [4.0, 6.0]`. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: Result tensor, new created. + public static func / (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(left.shape) + let outAddress = tensor.contentsAddress + let inAddress = left.contentsAddress + var scalar:Float = rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsdiv(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Tensor-Scalar in-place arithmetic operations. + + /// A tensor plus a scalar variable. Ex. `[2, 3] + 0.5 --> [2.5, 3.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: + @discardableResult public static func &+ (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let outAddress = left.contentsAddress + let inAddress = left.contentsAddress + var scalar = rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return left + } + + /// A tensor substract a scalar variable. Ex. `[2, 3] - 0.5 --> [1.5, 2.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: + @discardableResult public static func &- (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let outAddress = left.contentsAddress + let inAddress = left.contentsAddress + var scalar = -rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return left + } + + /// A tensor multiply a scalar variable. Ex. `[2, 3] * 0.5 --> [1.0, 1.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: left tensor after calculated. + @discardableResult public static func &* (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let outAddress = left.contentsAddress + let inAddress = left.contentsAddress + var scalar:Float = rightScalar.floatValue + let count = vDSP_Length(left.count) + vDSP_vsmul(inAddress, 1, &scalar, outAddress, 1, count) + return left + } + + /// A tensor divide a scalar variable. Ex. `[2, 3] / 0.5 --> [4.0, 6.0]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in left tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - left: A tensor object + /// - rightScalar: A scalar variable + /// - Returns: + @discardableResult public static func &/ (left: Tensor, rightScalar: SupportedScalarDataType) -> Tensor { + let address = left.contentsAddress + let count = vDSP_Length(left.count) + var scalar: Float = rightScalar.floatValue + vDSP_vsdiv(address, 1, &scalar, address, 1, count) + return left + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Scalar-Tensor arithmetic operations. + + /// A scalar plus a tensor variable. Ex. ` 0.5 + [2, 3] --> [2.5, 3.5]`. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + public static func + (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(right.shape) + let outAddress = tensor.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + + /// A scalar substracts a tensor variable. Ex. ` 0.5 - [2, 3] --> [-1.5, -2.5]`. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + public static func - (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(right.shape) + let outAddress = tensor.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + var scale:Float = -1.0 + let count = vDSP_Length(right.count) + vDSP_vsmsa(inAddress, 1, &scale, &scalar, outAddress, 1, count) + return tensor + } + + /// A scalar multiplies a tensor variable. Ex. ` 0.5 * [2, 3] --> [1.0, 1.5]`. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + public static func * (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(right.shape) + let outAddress = tensor.contentsAddress + let inAddress = right.contentsAddress + var scalar:Float = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_vsmul(inAddress, 1, &scalar, outAddress, 1, count) + return tensor + } + + /// A scalar divides by a tensor variable. Ex. ` 0.5 / [2, 3] --> [0.25, 0.16]`. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + public static func / (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(right.shape) + let outAddress = tensor.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_svdiv(&scalar, inAddress, 1, outAddress, 1, count) + return tensor + } + + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Scalar-Tensor in-place arithmetic operations. + + /// A scalar plus a tensor variable. Ex. ` 0.5 + [2, 3] --> [2.5, 3.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in right tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result right tensor. + @discardableResult public static func &+ (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let outAddress = right.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_vsadd(inAddress, 1, &scalar, outAddress, 1, count) + return right + } + + /// A scalar substracts a tensor variable. Ex. ` 0.5 - [2, 3] --> [-1.5, -2.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in right tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result right tensor. + @discardableResult public static func &- (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let outAddress = right.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + var scale:Float = -1.0 + let count = vDSP_Length(right.count) + vDSP_vsmsa(inAddress, 1, &scale, &scalar, outAddress, 1, count) + return right + } + + /// A scalar multiplies a tensor variable. Ex. ` 0.5 * [2, 3] --> [1.0, 1.5]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in right tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + @discardableResult public static func &* (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let outAddress = right.contentsAddress + let inAddress = right.contentsAddress + var scalar:Float = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_vsmul(inAddress, 1, &scalar, outAddress, 1, count) + return right + } + + /// A scalar divides by a tensor variable. Ex. ` 0.5 / [2, 3] --> [0.25, 0.16]`. + /// + /// - Note: This is an __in place__ operation. + /// Result stored in right tensor and return the calcualted left tensor. + /// + /// - Parameters: + /// - leftScalar: A scalar variable + /// - right: A tensor object + /// - Returns: Result tensor, new created. + @discardableResult public static func &/ (leftScalar: SupportedScalarDataType, right: Tensor) -> Tensor { + let outAddress = right.contentsAddress + let inAddress = right.contentsAddress + var scalar = leftScalar.floatValue + let count = vDSP_Length(right.count) + vDSP_svdiv(&scalar, inAddress, 1, outAddress, 1, count) + return right + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Slice tensor management + + + /// This function generate a `Tensor` object with attribute `sliceMarker` setting to `true`. + /// For cases where batch data samples stored in a single tensor and user wants to access one of the samples, + /// this function should be used. + /// + /// ## `batchIndex` + /// The argument `batchIndex` let the function knows which sample to fetch + /// and Serrano always assume the first dimension is the batch size. + /// For example: + /// ``` + /// /// Suppose we have a tensor with dimensions [2, 3, 4] + /// let tensor = ... + /// /// This get the 1st batch of the raw tensor and the slice dimensions is [3, 4] + /// let slice = tensor.batchSlice(0) + /// ``` + /// + /// ## 'nil' result + /// The function may return `nil` result in two cases: + /// - The `batchIndex` is larger than the `batch size - 1` + /// - The tensor object itself does not have at least 2 dimensions + /// + /// - Parameter batchIndex: the batch index + /// - Returns: `Tensor` object + public func batchSlice(_ batchIndex: Int) -> Tensor? { + guard self.shape.shapeArray.count >= 2 else { + SerranoLogging.errorLogging(message: "Tensor \(self.description) trying to generate a batchSlice at index \(batchIndex), but has only \(self.shape.shapeArray.count) dimensions", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + guard batchIndex < self.shape.shapeArray[0] else { + SerranoLogging.errorLogging(message: "Tensor \(self.description) trying to generate a batchSlice at index \(batchIndex), but the batch size is \(self.shape.shapeArray[0])", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + let count = self._shape.shapeArray.suffix(from: 1).reduce(1, *) + let address = self._dataMemoryBaseAdrress + count * batchIndex + let sliceShape = TensorShape(dataType: self._shape.dataType, shape: Array( self._shape.shapeArray.suffix(from: 1))) + let sliceTensor = Tensor(sliceTensorFrom: self, sliceContentAddress: address, + count: count, shape: sliceShape, index: batchIndex) + return sliceTensor + } + + /// Check if a slice tensor object belong to this tensor. + /// + /// - Note: If the passed in tensor is not a slice tensor, always returns `false`. + /// + /// - Parameter slice: slice tensor + /// - Returns: Bool. Result + public func containsSlice(_ slice: Tensor) -> Bool { + guard slice.isSliceTensor == true else { + SerranoLogging.warningLogging(message: "Trying to check a non-silce tensor object.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } + return slice.sliceRootTensor! == self + } + + /// Get the bytes offset for a slice tensor spawned from this tensor. + /// + /// - Note: The function will first check if contains this slice, if not it will return `nil` + /// + /// - Parameter slice: slice object + /// - Returns: offset in bytes + public func slicedTensorOffset(_ slice: Tensor) -> Int? { + guard self.containsSlice(slice) else { + return nil + } + return slice.count * slice.sliceIndex! + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Slice tensor methods + + /// The bytes offset from a slice tensor's root tensor. + /// + /// - Note: Return `nil` if this tensor is not a slice tensor. + /// + /// - Returns: the bytes offset + public func bytesOffsetFromRootTensor() -> Int? { + guard self.isSliceTensor else { + return nil + } + var offset = self.sliceParentTensor!.slicedTensorOffset(self)! + var tensor = self.sliceParentTensor! + while tensor != self.sliceRootTensor { + offset += tensor.sliceParentTensor!.slicedTensorOffset(tensor)! + tensor = tensor.sliceParentTensor! + } + return offset + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Protocol TensorSymbol + + /// Return self + /// + /// - Returns: tensor + public func evaluate() -> [DataSymbolSupportedDataType]? { + return [self] + } + + /// Add new symbol to `inBounds`. + /// Should check duplicate. + public func addToInBound(_ symbol: GraphSymbol) { + let s = symbol as! SerranoGraphSymbol + if !(self.inBoundsWeak.contains {$0.value == s}) { + let weakSymbol = WeakSerranoGraphSymbol(value: s) + self.inBoundsWeak.append(weakSymbol) + } + } + + /// Add new symbol to `outBounds` + /// Should check duplicate. + public func addToOutBound(_ symbol: GraphSymbol) { + let s = symbol as! SerranoGraphSymbol + if !(self.outBoundsWeak.contains {$0.value == s}) { + let weakSymbol = WeakSerranoGraphSymbol(value: s) + self.outBoundsWeak.append(weakSymbol) + } + } + + /// A tensor object could not be binded to another tensor. + /// - Warning: This function will do nonthing and always return `false`. + /// A error logging will be gaven. + /// + /// - Parameter data: data. Should be a `Tensor` object. + /// - Returns: always `False` + public func bindData(_ data:DataSymbolSupportedDataType) -> Bool { + SerranoLogging.errorLogging(message: "Tensor symbol \(self.symbolLabel) is a tensor object conforms to TensorSymbol. " + + "It cannot bind to another tensor object.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } +} + + diff --git a/Source/Serrano/graph/computation_graph.swift b/Source/Serrano/graph/computation_graph.swift new file mode 100644 index 0000000..ea27c7e --- /dev/null +++ b/Source/Serrano/graph/computation_graph.swift @@ -0,0 +1,708 @@ +// +// computation_graph.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + +/** +## Intro +The implementation of `Graph`. +A `ComputationGraph` just a set of symbols and connections between them. +The graph computes the output tensors stage by stage. + +A typical usage: + +```swift + +let graph = ComputationGraph() + +let a = graph.tensor("A", shape: TensorShape(dataType: .float, shape: [2,3])) +let b = graph.tensor("B", shape: TensorShape(dataType: .float, shape: [2,3])) +let (c, op) = graph.operation("", inputSymbols: [a, b], op: PowOperator()).first +``` + +## ComputationGraph V.S. Model +`ComputationGraph` is low-level abstraction of machine learning models. +It has two basic funtions: + - forward. Compute results from input to output + - backward. Compute and update data symbols that are differentiable use optimizer +User needs to call `forward(:)` and `backward(:)` manually to do training and `ComputationGraph` +is not aware of _loss function_. +If you want to use loss function if a graph, you need add it into this graph as an operator symbol. + +`Model` is a higher level abstraction inherited from `ComputationGraph`. +It has all functions `ComputationGraph` has and beyond that: + - Loss function. `Model` could setup loss function to do the backward training. + 'ComputationGraph' + - High level training functions. `Model` could automatically repeat `forward(:)` and `backward(:)` util + reaches conditions users setup like max number of epoch, early stop etc. + - High level prediction functions. +*/ +public class ComputationGraph: Graph { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Conforms to `Graph` + public var symbols: [String: GraphSymbol] + + /// Conforms to `Graph` + public var graphLabel: String + + /// Conforms to `Graph`. + /// Default is `false`. + public var trainable: Bool = false + + /// Description + public var description: String { + get { + return "ComputationGraph(\(self.graphLabel))" + } + } + + /// Optimizer of this graph doing backward training. + /// Could be `nil` if just do forward calcualtion. + public var optimizer: Optimizer? = nil + + /// This attribute indicates if the graph has been sorted. + /// Initial value is `false`. + public var sorted: Bool = false + + /// A dictionary stores the sorted symbols after applying topology sorting. + /// The key is the depth value, and the value is the list of symbols in this depth stage. + public var symbolStages: [Int: [GraphSymbol]] = [Int: [GraphSymbol]]() + + /// Counter of backward training + public var epoch: Int { + get { + return self._epoch + } + } + + /// Counter of backward training + internal var _epoch: Int = 0 + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init + /// + /// - Parameters: + /// - symbols: symbols + /// - graphLabel: graphLabel + /// - trainable: trainable + public init(symbols: [String: SerranoGraphSymbol], graphLabel: String, trainable: Bool) { + self.symbols = symbols + self.graphLabel = graphLabel + self.trainable = trainable + } + + /// Convenience init + /// + /// - Parameter graphLabel: graphLabel + public convenience init(_ graphLabel: String? = nil) { + if graphLabel != nil { + self.init(symbols: [String : SerranoGraphSymbol](), graphLabel: graphLabel!, trainable: false) + } else { + self.init(symbols: [String : SerranoGraphSymbol](), graphLabel: "Serrano Graph", trainable: false) + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Util + + /// Generate a default label for a symbol. + /// + /// - Parameter type: SymbolType + /// - Returns: return value + public func defaultSymbolLabel(_ type: SymbolType) -> String { + return "Serrano Graph" + } + + /// Get this graph's all data symbols + /// + /// - Returns: list of data symbol + public func dataSymbols() -> [DataSymbol] { + return self.symbols.filter {$0.value.symbolType.isDataSymbol()}.map {$0.value as! DataSymbol} + } + + + /// Get his graph's all operator symbols + /// + /// - Returns: list of operator symbols + public func opSymbols() -> [OperatorSymbol] { + return self.symbols.filter {$0.value.symbolType == SymbolType.Operator}.map {$0.value as! OperatorSymbol} + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods conforms to `Graph` + + /// Add a `TensorSymbol` to the graph. + /// + /// - Parameter label: label + /// - Returns: A `TensorSymbol` + @discardableResult + public func tensor(_ label: String? = nil, shape: TensorShape) -> TensorSymbol { + // new symbol, graph unsorted + self.sorted = false + + var symbolLabel = label + if symbolLabel == nil { symbolLabel = self.defaultSymbolLabel(.Tensor) } + let tensorSymbol = SerranoTensorSymbol(symbolLabel!, dataSource: .User, shape: shape) + self.symbols[tensorSymbol.UID] = tensorSymbol + // logging + SerranoLogging.stdLogging(message: "New TensorSymbol added to graph [\(self.graphLabel)]", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.Regular) + + return tensorSymbol + } + + /// Add a `ScalarSymbol` to the graph. + /// + /// - Parameter label: label + /// - Returns: A `ScalarSymbol` + @discardableResult + public func scalar(_ label: String? = nil, dataType: TensorDataType) -> ScalarSymbol { + // new symbol, graph unsorted + self.sorted = false + + var symbolLabel = label + if symbolLabel == nil { symbolLabel = self.defaultSymbolLabel(.Scalar) } + let scalarSymbol = SerranoScalarSymbol(symbolLabel!, dataType: dataType, dataSource: .User) + self.symbols[scalarSymbol.UID] = scalarSymbol + // logging + SerranoLogging.stdLogging(message: "New ScalarSymbol added to graph [\(self.graphLabel)]", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.Regular) + + return scalarSymbol + } + + /// Add a `OperatorSymbol` to the graph. + /// This function will also update inBounds and outBounds information for involving symbols. + /// + /// - Parameters: + /// - inputs: input array of `TensorSymbol` + /// - operator: A `ComputableOperator` instance + /// - Returns: + /// - outputTensorSymbols: output tensor symbols + /// - operatorSymbol: added operator symbol + /// - paramSymbols: Parameter symbols attached to this operator. Empty array if not available. + @discardableResult + public func operation(_ label: String? = nil, inputs: [TensorSymbol], op: ComputableOperator) -> (outputTensorSymbols: [TensorSymbol], operatorSymbol: OperatorSymbol, paramSymbols: [GraphSymbol]) { + // new symbol, graph unsorted + self.sorted = false + + var symbolLabel = label + if symbolLabel == nil { symbolLabel = self.defaultSymbolLabel(.Scalar) } + let opSymbol = SerranoOperatorSymbol(symbolLabel!, serranoOperator: op, inputSymbols: inputs) + self.addSymbols(opSymbol) + + // logging + SerranoLogging.stdLogging(message: "New OperatorSymbol added to graph [\(self.graphLabel)]", + file: "\(#file)", function: "\(#function)", line: "\(#line)", + loggingLevel: SerranoLoggingType.Regular) + + // output symbols bounds process + let outTensorSymbols = opSymbol.outputSymbols() + for outputSymbol in outTensorSymbols { + let symbol = outputSymbol as GraphSymbol? + symbol!.addToInBound(opSymbol) + + opSymbol.addToOutBound(outputSymbol) + + self.addSymbols(outputSymbol) + } + + // inputs symbols bounds process + for inputSymbol in inputs { + let symbol = inputSymbol as GraphSymbol? + symbol!.addToOutBound(opSymbol) + + self.addSymbols(inputSymbol) + } + + // param symbols + let paramSymbols = op.paramSymbols() + for paramSymbol in paramSymbols { + let symbol = paramSymbol as GraphSymbol? + symbol!.addToOutBound(opSymbol) + + // add to opSymbol + opSymbol.addToInBound(paramSymbol) + opSymbol.addToParamSymbols(paramSymbol) + + self.addSymbols(paramSymbol) + } + + return (outTensorSymbols, opSymbol, paramSymbols) + } + + /// Bind data to `TensorSymbol` or `ScalarSymbol` which need feeding data from users. + /// This function will try to bind every entry in `data` to tensor or scalar symbol in `symbols`. + /// If could find an entry has same UID in `symbols` for passed in data, it will be ignored. + /// + /// - Note: This function does not verify any restrictions. + /// + /// - Parameter data: A dictinary whose key is `UID` of a symbol + /// and the value is a `DataSymbolSupportedDataType` object. + public func bindData(_ data: [String: DataSymbolSupportedDataType]) { + for (UID, entry) in data { + let symbol = self.symbols[UID] + // not find + if symbol == nil { + SerranoLogging.warningLogging(message: "Could not find symbol with UID (\(UID)) in \(self.description).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + continue + } + + guard symbol! is DataSymbol else { + SerranoLogging.errorLogging(message: "Trying to bind data to symbol \(symbol!), but this is not a data symbol", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Error raised by Serrano. Check log for details.") + } + + guard (symbol! as! DataSymbol).bindData(entry) else { + SerranoLogging.errorLogging(message: "Trying to bind data to symbol \(symbol!), but failed. Check log for details.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Error raised by Serrano. Check log for details.") + } + } + } + + /// Compute the whole graph from inputs to outputs. + /// If there's any error during calculation or pre-checking, return `nil`. + /// + /// - Parameter mode: computation mode + /// - Returns: Array of tensors/Scalar or `nil` if error happens + public func forward(mode: OperatorComputationMode) -> [DataSymbolSupportedDataType]? { + // compute in stage order + self.stageOrderCalculate(mode: mode) + + // group last stage tensors and return + let lastStage = self.symbolStages.keys.max()! + let outputDataSymbols = self.symbolStages[lastStage]?.filter { $0.symbolType.isDataSymbol()} + + guard outputDataSymbols != nil else { + SerranoLogging.errorLogging(message: "Could not gather last stage tensors. Maybe graph defined is not valid.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + return (outputDataSymbols as! [DataSymbol]).map {$0.bindedData!} + } + + /// Backward computing the grads for updatable data symbols. + /// + /// This function supposes that graph already been verifed and done at least onece forward computation. + /// If not, it may cause unexpected error. + /// + /// - Note: If this graph is not `trainable`, `fatalError` will be raised. + /// + /// - Parameters: + /// - mode: computation mode + public func backward(mode: OperatorComputationMode) { + // update parameters + self.dataSymbolsUpdate(mode) + + // windup + self.windup() + + // increase epoch + self._epoch += 1 + SerranoLogging.stdLogging(message: "Finish epoch \(self._epoch).", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: SerranoLoggingType.LowLevel) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Forawrd related methods + + + /// Prepare before forwarding. + /// Usually should be called just before 1st fowarding. + /// However, if user chagne graph strcture, this should be called again. + public func forwardPrepare() { + // sort + self.sortGraph() + + // user input bind check + var (valid, msg) = self.userInputBindCheck() + guard valid else { + SerranoLogging.errorLogging(message: "Some symbols havn't been binded. Detail:\(msg)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + // allocate tensors for needs + self.allocateTensors() + + // verify + (valid, msg) = self.verifyGraph() + guard valid else { + SerranoLogging.errorLogging(message: "Graph is not valid. Detail:\(msg)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + + /// Use topology sorting to sort all symbols into different stages. + /// Store results in `symbolStages`. + /// Currently, only supports __directed acyclic graph__ (i.e. no directed cycles). + /// + /// - Note: If there is no input data symbols, `fatalError` will be raised. + /// + /// # Algorithm + /// Use DFS(Depth-First Search) to construct the staged information. + /// + /// 1. For each data symbol whose `dataSource` is `SymbolDataSource.User`: + /// + /// I. Add this symbol to a list with depth 0. + /// + /// II. Use DFS to starting from this symbol following outBounds path, + /// and add the visiting symbol and corresponding depth into list. + /// Each time gos deeper, depth plus 1. + /// + /// III. If the visiting symbol is already in the statck, set depth to max(this visiting depth, existing depth). + /// 2. Then according the list information, add symbols in same stage into `symbolStages`. + public func sortGraph() { + // get input data symbols + let inputDataSymbols = self.symbols.filter { (UID, symbol) -> Bool in + return symbol.symbolType.isDataSymbol() && symbol.inBounds.count == 0 + } + + guard inputDataSymbols.count > 0 else { + SerranoLogging.errorLogging(message: "Could not found input data symbols in this graph (\(self.graphLabel))." + + "Thus cannot generate computation graph.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + // DFS + var symbolDepthList = [SerranoGraphSymbol: Int]() // key is UID + for (_, symbol) in inputDataSymbols { + self.visitSymbol(symbol, symbolDepthList: &symbolDepthList, currentDepth: 0) + } + + // add stage information + for (symbol, depth) in symbolDepthList { + if self.symbolStages[depth] == nil { self.symbolStages[depth] = [SerranoGraphSymbol]() } + self.symbolStages[depth]!.append(symbol) + } + + // set attribute + self.sorted = true + } + + /// Visist a symbol and its outBounds symbols in DFS manner. + /// + /// - Parameters: + /// - symbol: visiting symbol + /// - symbolDepthList: symbolDepthList. The passed in dictionary will be mutated. + /// - currentDepth: current visiting depth + public func visitSymbol(_ symbol: GraphSymbol, symbolDepthList: inout [SerranoGraphSymbol: Int], currentDepth: Int) { + let serranoSymbol = symbol as! SerranoGraphSymbol + if symbolDepthList[serranoSymbol] == nil { + symbolDepthList[serranoSymbol] = currentDepth + } else { + symbolDepthList[serranoSymbol] = max(symbolDepthList[serranoSymbol]!, currentDepth) + } + + for outSymbol in symbol.outBounds { + self.visitSymbol(outSymbol, symbolDepthList: &symbolDepthList, currentDepth: currentDepth + 1) + } + } + + /// This functions verifies: + /// - All symbols should have been binded to data + /// - Shape compatibility between connected operators and tensors + /// + /// If any incorrectness found, return `false` and associated message. + /// + /// - Note: This function assumes graph already been sorted and attribute `symbolStages` already has stage info. + /// + /// - Returns: `valid` represent if passing the verification and error msg if has + public func verifyGraph() -> (valid: Bool, msg: String) { + // check all data symbols have been binded + for (_, symbol) in self.symbols { + if symbol.symbolType == .Scalar { + let scalarSymbol = symbol as! ScalarSymbol + guard scalarSymbol.bindedData != nil else { + return (false, "ScalarSymbol(\(scalarSymbol.symbolLabel)) needs user feeded data but bindedData is nil.") + } + } else if symbol.symbolType == .Tensor { + let tensorSymbol = symbol as! TensorSymbol + guard tensorSymbol.bindedData != nil else { + return (false, "TensorSymbol(\(tensorSymbol.symbolLabel)) needs user feeded data but bindedData is nil.") + } + } + } + + // check if sorted + guard self.sorted else { return (false, "Graph has not been sorted.") } + + // check shape matching + let (valid, msg) = self.checkShapeChain() + guard valid else { return (false, "\(msg)") } + + return (true, "") + } + + /// This function check a sorted graph's every path to see if all connected operators + /// and symbols could match each other considering `TensorShape`. + /// + /// ## Checking + /// ``` + /// For each operator symbol in this stage: + /// I. Get input tensors from inBounds and output tensors from outBounds. + /// Assign tensors to operator's inputTensors and outputTensors. + /// II. Call inputOutputTensorCheck(). + /// a). If return true, mark operator's disableInputOutputCheck to true. + /// Continue. + /// b). If return false, return false and related errror msg. + /// ``` + /// + /// - Note: This internal function assumes the graph's all symbols have been binded. + /// + /// - Returns: validation result and error message if has + public func checkShapeChain() -> (valid: Bool, msg: String) { + let opSymbols = self.symbols.filter { (UID, symbol) -> Bool in + return symbol.symbolType == SymbolType.Operator + } + + for (_, symbol) in opSymbols { + var opSymbol = symbol as! OperatorSymbol + + // setup input tensors + // Note here should use `inputSymbols` not `inBounds`. + // Cause `inBounds` also includes `paramSymbols`. + let inputTensors = opSymbol.inputSymbols.map { $0.bindedData! } + opSymbol.serranoOperator.inputTensors = (inputTensors as! [Tensor]) + + // setup output tensors + let outputTensors = opSymbol.outBounds.map { ($0 as! TensorSymbol).bindedData! } + opSymbol.serranoOperator.outputTensors = (outputTensors as! [Tensor]) + + // bind parameter + let paramSymbols = opSymbol.inBounds.filter({ (symbol) -> Bool in + // not in input + return !opSymbol.inputSymbols.contains { $0.UID == symbol.UID} + }) + opSymbol.serranoOperator.bindParamSymbols(paramSymbols) + + let (pass, msg) = opSymbol.serranoOperator.inputOutputTensorsCheck() + guard pass else { + return (false, "Operator symbol \(opSymbol.symbolLabel) failed to pass inputOutputTensorsCheck(). " + + "Details: \(msg)") + } + } + + return (true, "") + } + + /// Check if all user data source symbols have been binded. + /// + /// - Returns: if pass checking and related error msg if has. + public func userInputBindCheck() -> (valid: Bool, msg: String) { + let dataSymbols = self.symbols.filter { $0.value.symbolType.isDataSymbol() } + for (_, symbol) in dataSymbols { + switch symbol.symbolType { + case .Tensor: + let tensorSymbol = symbol as! TensorSymbol + if tensorSymbol.dataSource == .User { + guard tensorSymbol.bindedData != nil else { + return (false, "Tensor symbol (\(tensorSymbol.symbolLabel)) should bind data feeding from user but is nil.") + } + } + case .Scalar: + let scarlarSymbol = symbol as! ScalarSymbol + if scarlarSymbol.dataSource == .User { + guard scarlarSymbol.bindedData != nil else { + return (false, "Scalar symbol (\(scarlarSymbol.symbolLabel)) should bind data feeding from user but is nil.") + } + } + default: + continue + } + } + + return (true, "") + } + + + /// Allocate tensors for all tensor symbols. + /// This is used when training a network from zero. + /// + /// - Note: This method ignore existing binding, allocate new tensors for all tensor symbols. + public func allocateAllTensors() { + for (_, symbol) in self.symbols { + if symbol.symbolType == .Tensor { + var tensorSymbol = symbol as! TensorSymbol + tensorSymbol.bindedData = SerranoResourceManager.globalManager.allocateUnamangedTensor(tensorSymbol.shape) + } + } + } + + /// Allocate tensors for all tensor symbols not feeding from users. + /// Currently, user `SerranoResourceManager.globalManager` to allocate unmanaged resources. + /// + /// TODO: Do memory dependency analize, reuser tensors. + public func allocateTensors() { + for (_, symbol) in self.symbols { + if symbol.symbolType == .Tensor { + var tensorSymbol = symbol as! TensorSymbol + + if tensorSymbol.dataSource == .User || tensorSymbol.bindedData != nil { + continue + } else { + tensorSymbol.bindedData = SerranoResourceManager.globalManager.allocateUnamangedTensor(tensorSymbol.shape) + } + } + } + } + + + /// Stage by stage, run all operators. + /// + /// ## Algorithm + /// ```` + /// for stage i in [0, n]: + /// run all operators in stage i simutaneously + /// ``` + /// + /// - Note: Graph should have been sorted. Else `fatalError` will be raised. + internal func stageOrderCalculate(mode: OperatorComputationMode) { + // check sorted + guard self.sorted else { + SerranoLogging.errorLogging(message: "Graph not sorted. Abort calculation.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + let stageWorkGroup = DispatchGroup() + let begginTime = CFAbsoluteTimeGetCurrent() + for stage in self.symbolStages.keys.sorted() { + for symbol in self.symbolStages[stage]! { + if symbol.symbolType == .Operator { + var opSymbol = symbol as! OperatorSymbol + stageWorkGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + opSymbol.serranoOperator.disableInputOutputCheck = true + opSymbol.serranoOperator.compute(mode) + stageWorkGroup.leave() + } + } + } + // wait all complete in this stage + stageWorkGroup.wait() + } + + SerranoLogging.stdLogging(message: "Finish forward for graph \(self.graphLabel) in \(CFAbsoluteTimeGetCurrent() - begginTime) seconds ", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: SerranoLoggingType.LowLevel) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Backward related methods + + + /// Prepare workd. + internal func backwardPrepare() { + guard self.trainable else { + SerranoLogging.errorLogging(message: "Graph is not trainable.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("") + } + + // check opmizer not nil + guard self.optimizer != nil else { + SerranoLogging.errorLogging(message: "Optimizer of graph is nil. Cannot do backward training.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("") + } + + // initialize grad value tensors at the very first training + if self._epoch == 0 { + self.allocateTensorsForGrads() + } + + // let optimizer do prepare work if need + self.optimizer!.prepare(self) + } + + /// Allocate tensors and initialize scalar values for grads of data symbol. + /// + /// ## Initialization strategy + /// We will only look at operators with `true` values for attribute `enabledParameterUpdate`, + /// and will initialize currentGrad of these operator symbols' inbound data symbols with + /// `true` value for attribute `updateble`. + internal func allocateTensorsForGrads() { + for opSymbol in self.opSymbols() { + if opSymbol.enabledParameterUpdate { + for dataSymbol in opSymbol.inBounds { + if dataSymbol.symbolType.isDataSymbol() { + switch(dataSymbol.symbolType) { + case SymbolType.Scalar: + var scalarSymbol = dataSymbol as! ScalarSymbol + scalarSymbol.currentGrad = Float(0.0) + case SymbolType.Tensor: + var tensorSymbol = dataSymbol as! TensorSymbol + tensorSymbol.currentGrad = SerranoResourceManager.globalManager.allocateUnamangedTensor(tensorSymbol.shape) + default: + continue + } + } + } + } + } + } + + /// This function do updating during backward training. + /// + /// For operator symbols at each stage starting from last to first, + /// calculate the grads for all inbound data symbols. + /// Then update values for those are `updateble`. + internal func dataSymbolsUpdate(_ mode: OperatorComputationMode) { + let workGroup = DispatchGroup() + for (_, symbols) in (self.symbolStages.sorted {$0.0.key > $0.1.key}) { // desending + for symbol in (symbols.filter {$0.symbolType == SymbolType.Operator}) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + let opSymbol = symbol as! OperatorSymbol + // TODO: Improve API design. + // In this function operator will allocate new tensors/scalars holding grads, which is not good. + // The idea is that graph itself do all memory allocation, so later we can do optimization. + let gradsInfo = opSymbol.serranoOperator.gradCompute(mode) + for (label, gradValue) in gradsInfo { + // get corresponding inpbound data symbol + var inboundSymbol = opSymbol.inboundSymbolForGradLabel(label) + guard inboundSymbol != nil else { + SerranoLogging.errorLogging(message: "Unexpexted nil object.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("") + } + + if inboundSymbol!.updatable { + // up grads + let gradRelatedOutSymbols = opSymbol.gradRelatedOutputSymbols(onInboundSymbol: inboundSymbol!) + let upGrads = gradRelatedOutSymbols.flatMap {$0.currentGrad} // take out nil + + inboundSymbol!.backwardUpdate(self.optimizer!, gradValue: gradValue, upGrads: upGrads) + } + } + workGroup.leave() + } + } + } + workGroup.wait() + } + + /// This function does windup work after updating all grads in this graph. + internal func windup() { + + } + +} + diff --git a/Source/Serrano/graph/symbols/graph_symbol.swift b/Source/Serrano/graph/symbols/graph_symbol.swift new file mode 100644 index 0000000..575ffa6 --- /dev/null +++ b/Source/Serrano/graph/symbols/graph_symbol.swift @@ -0,0 +1,142 @@ +// +// graph_symbol.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + +typealias WeakSerranoGraphSymbol = WeakRef + +/** +Implementation of `GraphSymbol` +*/ +public class SerranoGraphSymbol: GraphSymbol, Hashable { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Symbol type. Conforms to `GraphSymbol`. + public var symbolType: SymbolType + + /// Unique symbol ID. Conforms to `GraphSymbol`. + /// - Note: A 6-length `String` consists of `[a-zA-Z0-9]` + public var UID: String + + /// Readable label. Conforms to `GraphSymbol`. + public var symbolLabel: String + + /// Inbound symbols list. Conforms to `GraphSymbol`. + /// To prevent from cycle reference, we dynamic constructing this attribute from `inBoundsWeak`. + public var inBounds: [GraphSymbol] { + get { + return self.inBoundsWeak.filter {$0.value != nil}.map {$0.value!} + } + set(bounds) { + for symbol in bounds { + self.addToInBound(symbol) + } + } + } + + /// Outbound symbols list. Conforms to `GraphSymbol`. + /// To prevent from cycle reference, we dynamic constructing this attribute from `inBoundsWeak`. + public var outBounds: [GraphSymbol] { + get { + return self.outBoundsWeak.filter {$0.value != nil}.map {$0.value!} + } + set(bounds) { + for symbol in bounds { + self.addToOutBound(symbol) + } + } + } + + /// Hash value. + /// Conforms to `equatable` protocol. + public var hashValue: Int { + get { + return self.UID.hashValue + } + } + + /// Weak reference array of inbounds objects + internal var inBoundsWeak: [WeakSerranoGraphSymbol] + + /// Weak reference array of outbounds objects + internal var outBoundsWeak: [WeakSerranoGraphSymbol] + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init + /// + /// - Parameters: + /// - symbolType: symbolType + /// - label: label + /// - inBounds: inBounds + /// - outBounds: outBounds + public init(label: String, symbolType: SymbolType, + inBounds: [GraphSymbol], outBounds: [GraphSymbol]) { + self.symbolType = symbolType + self.symbolLabel = label + self.inBoundsWeak = [WeakSerranoGraphSymbol]() + self.outBoundsWeak = [WeakSerranoGraphSymbol]() + self.UID = serranoSymbolUIDGenerate() + + // add + for symbolIn in inBounds { + self.addToInBound(symbolIn) + } + for symbolOut in outBounds { + self.addToOutBound(symbolOut) + } + } + + /// Convenience init + /// + /// - Parameters: + /// - symbolType: symbolType + /// - label: label + public convenience init(_ label: String = "", symbolType: SymbolType) { + self.init(label: label, symbolType: symbolType, inBounds: [GraphSymbol](), outBounds: [GraphSymbol]()) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + public func evaluate() -> [DataSymbolSupportedDataType]? { + fatalError("Should not be called.") + } + + /// Add new symbol to `inBounds`. + /// Should check duplicate. + public func addToInBound(_ symbol: GraphSymbol) { + let s = symbol as! SerranoGraphSymbol + if !(self.inBoundsWeak.contains {$0.value == s}) { + let weakSymbol = WeakSerranoGraphSymbol(value: s) + self.inBoundsWeak.append(weakSymbol) + } + } + + /// Add new symbol to `outBounds` + /// Should check duplicate. + public func addToOutBound(_ symbol: GraphSymbol) { + let s = symbol as! SerranoGraphSymbol + if !(self.outBoundsWeak.contains {$0.value == s}) { + let weakSymbol = WeakSerranoGraphSymbol(value: s) + self.outBoundsWeak.append(weakSymbol) + } + } + + /// Conforms to `equatable` protocol + /// + /// - Parameters: + /// - lhs: left compare + /// - rhs: right compare + /// - Returns: return value + public static func == (lhs: SerranoGraphSymbol, rhs: SerranoGraphSymbol) -> Bool { + return lhs.UID == rhs.UID + } +} diff --git a/Source/Serrano/graph/symbols/operator_symbol.swift b/Source/Serrano/graph/symbols/operator_symbol.swift new file mode 100644 index 0000000..64bbffd --- /dev/null +++ b/Source/Serrano/graph/symbols/operator_symbol.swift @@ -0,0 +1,92 @@ +// +// operator_symbol.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + + +/** +Implementaion of `OperatorSymbol` +*/ +public class SerranoOperatorSymbol: SerranoGraphSymbol, OperatorSymbol { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator instance + public var serranoOperator: ComputableOperator + + public var inputTensorShapes: [TensorShape] + + /// Input tensor symbols + public var inputSymbols: [TensorSymbol] + + /// Params tensor symbols + public var paramSymbols: [DataSymbol] + + /// Control if update asscoiated operator's parameter + /// Default is `false` + public var enabledParameterUpdate = false + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated initalizers + /// + /// - Parameters: + /// - serranoOperator: serranoOperator description + /// - label: label description + /// - inBounds: inBounds description + /// - outBounds: outBounds description + public init(_ label: String = "Operator symbol", + serranoOperator: ComputableOperator, + inputSymbols: [TensorSymbol], + paramSymbols: [DataSymbol]? = nil, + inBounds: [GraphSymbol] = [GraphSymbol](), + outBounds: [GraphSymbol] = [GraphSymbol]()) { + self.serranoOperator = serranoOperator + self.inputSymbols = inputSymbols + + if paramSymbols == nil { + self.paramSymbols = [DataSymbol]() + } else { + self.paramSymbols = paramSymbols! + } + + self.inputTensorShapes = inputSymbols.map {$0.shape} + super.init(label: label, symbolType: .Operator, inBounds: inputSymbols, outBounds: [GraphSymbol]()) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Get output symbols of this operator. + /// The output shapes are decided by operaotr's function `outputShape(shapeArray: inputShapes)` + /// + /// - Note: This function will automatically add return symbols in `outBounds`. + /// + /// - Returns: Array of TensorSymbols. + public func outputSymbols() -> [TensorSymbol] { + var output = [TensorSymbol]() + + let inputShapes = self.inputSymbols.map { $0.shape } + let outputShapes = self.serranoOperator.outputShape(shapeArray: inputShapes) + guard outputShapes != nil else { + SerranoLogging.errorLogging(message: "Operator symbol (\(self.symbolLabel)) has invalid tensor symbols. It could not generate output tensor symbols. Check previous log for detail error info.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + for outShape in outputShapes! { + output.append(SerranoTensorSymbol(dataSource: .Calculation, shape: outShape)) + self.addToOutBound(output.last! as GraphSymbol) + } + + return output + } +} + diff --git a/Source/Serrano/graph/symbols/scalar_symbol.swift b/Source/Serrano/graph/symbols/scalar_symbol.swift new file mode 100644 index 0000000..4165b4e --- /dev/null +++ b/Source/Serrano/graph/symbols/scalar_symbol.swift @@ -0,0 +1,108 @@ +// +// scalar_symbol.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/** +Implementation of `ScalarSymbol` +*/ +public class SerranoScalarSymbol: SerranoGraphSymbol, ScalarSymbol { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Binded scalar value + public var bindedData: DataSymbolSupportedDataType? { + get { + return self._bindedData as? DataSymbolSupportedDataType + } + set(newValue) { + if newValue is SupportedScalarDataType? { + self._bindedData = newValue as! SupportedScalarDataType? + } else { + SerranoLogging.errorLogging(message: "Unexpexted data type. Expecte scalar type.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("") + } + } + } + + /// The data type + public var dataType: TensorDataType + + /// Data source + public var dataSource: SymbolDataSource + + /// If differentiable + public var updatable = false + + /// Current grad + public var currentGrad: DataSymbolSupportedDataType? + + /// If enabled history grads recording. + /// Default is `false`. + public var historyGradsEnabled = false + + /// grads + public var historyGrads: [DataSymbolSupportedDataType] = [SupportedScalarDataType]() as! [DataSymbolSupportedDataType] + + /// Scalar value + internal var _bindedData: SupportedScalarDataType? + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init + /// + /// - Parameters: + /// - dataType: dataType + /// - dataSource: dataSource + /// - bindedData: bindedData + /// - label: label + /// - inBounds: inBounds + /// - outBounds: outBounds + public init(dataType: TensorDataType, dataSource: SymbolDataSource, bindedData: SupportedScalarDataType? = nil, + label: String, inBounds: [GraphSymbol], outBounds: [GraphSymbol]) { + self.dataType = dataType + self._bindedData = bindedData + self.dataSource = dataSource + super.init(label: label, symbolType: .Scalar, inBounds: inBounds, outBounds: outBounds) + } + + /// Convenience init + /// + /// - Parameter dataType: dataType + public convenience init(_ label: String = "Scalar Symbol", dataType: TensorDataType, dataSource: SymbolDataSource) { + self.init(dataType: dataType, dataSource: dataSource, bindedData: nil, label: label, inBounds: [GraphSymbol](), outBounds: [GraphSymbol]()) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Bind to a scalar variable + /// + /// - Note: If passed in `data`'s type is uncompatible with attribute `dataType`, no further action will be taken. + /// Since when doing calculation, all data converted to `Float`. + /// + /// + /// - Parameter data: data. Should be a scalar variable (`Int`, 'Float' or `Double`). + /// - Returns: bind result + @discardableResult + public func bindData(_ data:DataSymbolSupportedDataType) -> Bool { + // check data type + guard (data is SupportedScalarDataType) else { + SerranoLogging.errorLogging(message: "Scalar symbol (\(self.symbolLabel)) expects a scalar object to bind. Given \(type(of: data)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } + + self._bindedData = data as! SupportedScalarDataType + + return true + } +} diff --git a/Source/Serrano/graph/symbols/tensor_symbol.swift b/Source/Serrano/graph/symbols/tensor_symbol.swift new file mode 100644 index 0000000..04eb73a --- /dev/null +++ b/Source/Serrano/graph/symbols/tensor_symbol.swift @@ -0,0 +1,115 @@ +// +// tensor_symbol.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/** +Implementation of `TensorSymbol` +*/ +public class SerranoTensorSymbol: SerranoGraphSymbol, TensorSymbol { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Data source. Conforms to `GraphSymbol`. + public var dataSource: SymbolDataSource + + /// Shape. Conforms to `GraphSymbol`. + public var shape: TensorShape + + /// Binded data. Conforms to `GraphSymbol`. + public var bindedData: DataSymbolSupportedDataType? { + get { + return self._bindedData + } + set(newValue) { + if newValue is Tensor? { + self._bindedData = newValue as! Tensor? + } else { + SerranoLogging.errorLogging(message: "Unexpexted data type. Expect Tensor object", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("") + } + } + } + + /// If differentiable + public var updatable = false + + /// Current grad + public var currentGrad: DataSymbolSupportedDataType? + + /// If enabled history grads recording. + /// Default is `false`. + public var historyGradsEnabled = false + + /// grads + public var historyGrads:[DataSymbolSupportedDataType] = [Tensor]() + + /// Binded tensor + internal var _bindedData: Tensor? + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init + /// + /// - Parameters: + /// - dataSource: dataSource + /// - bindedData: bindedData + /// - label: label + /// - inBounds: inBounds + /// - outBounds: outBounds + public init(dataSource: SymbolDataSource, bindedData: Tensor?, shape: TensorShape, + label: String, inBounds: [GraphSymbol], outBounds: [GraphSymbol]) { + self.dataSource = dataSource + self._bindedData = bindedData + self.shape = shape + super.init(label: label, symbolType: .Tensor, inBounds: inBounds, outBounds: outBounds) + } + + /// Covenient init + /// + /// - Parameters: + /// - label: label + /// - dataSource: dataSource + public convenience init(_ label: String = "Tensor Symbol", dataSource: SymbolDataSource, shape: TensorShape) { + self.init(dataSource: dataSource, bindedData: nil, shape: shape, + label: label, inBounds: [GraphSymbol](), outBounds: [GraphSymbol]()) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Bind to a tensor. + /// There two cases could not bind successfull: + /// 1. Passed in `data` is not a tensor object; + /// 2. Passed in tensor object has not compatible shape with attribute `shape`. + /// + /// - Parameter data: data. Should be a `Tensor` object. + /// - Returns: bind result + @discardableResult + public func bindData(_ data:DataSymbolSupportedDataType) -> Bool { + // check data type + guard data is Tensor else { + SerranoLogging.errorLogging(message: "Tensor symbol (\(self.symbolLabel)) expects a tensor object to bind. Given \(type(of: data)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } + + // check shape + let tensor = data as! Tensor + guard tensor.shape .== self.shape else { + SerranoLogging.errorLogging(message: "Tensor symbol (\(self.symbolLabel)) expects a tensor object with shape \(self.shape.description). Given \(tensor.shape.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return false + } + self._bindedData = tensor + + return true + } +} diff --git a/Source/Serrano/io/flatbuffer.swift b/Source/Serrano/io/flatbuffer.swift new file mode 100644 index 0000000..df72e88 --- /dev/null +++ b/Source/Serrano/io/flatbuffer.swift @@ -0,0 +1,14 @@ +// +// flatbuffer.swift +// Serrano +// +// Created by ZHONGHAO LIU on 11/2/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import LibFBSUtil + +public func libImport() { + LibFBSUtil.hello() +} diff --git a/Source/Serrano/model/base_model.swift b/Source/Serrano/model/base_model.swift new file mode 100644 index 0000000..1284f18 --- /dev/null +++ b/Source/Serrano/model/base_model.swift @@ -0,0 +1,12 @@ +// +// base_model.swift +// serrano +// +// Created by ZHONGHAO LIU on 9/19/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/// Base mode has train, predict, this kind of function, and work like keras function api diff --git a/Source/Serrano/model/sequential_model.swift b/Source/Serrano/model/sequential_model.swift new file mode 100644 index 0000000..0d1c6cb --- /dev/null +++ b/Source/Serrano/model/sequential_model.swift @@ -0,0 +1,12 @@ +// +// sequential_model.swift +// serrano +// +// Created by ZHONGHAO LIU on 9/19/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +//// only construct sequential model. work like keras sequential model diff --git a/Source/Serrano/operators/basic/binary/binary_op.metal b/Source/Serrano/operators/basic/binary/binary_op.metal new file mode 100644 index 0000000..6f0564f --- /dev/null +++ b/Source/Serrano/operators/basic/binary/binary_op.metal @@ -0,0 +1,72 @@ +// +// binary_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 6/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +namespace serrano_ios { + kernel void Add(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = inputA[gid.x] + inputB[gid.x]; + } + + kernel void Sub(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = inputA[gid.x] - inputB[gid.x]; + } + + kernel void Mult(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = inputA[gid.x] * inputB[gid.x]; + } + + kernel void Div(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = inputA[gid.x] / inputB[gid.x]; + } + + kernel void RDiv(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = inputB[gid.x] / inputA[gid.x]; + } + + kernel void Pow(device float* inputA [[ buffer(0) ]], + device float* inputB [[ buffer(1) ]], + device float* outputC [[ buffer(2) ]], + constant uint* count [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + outputC[gid.x] = pow(inputA[gid.x], inputB[gid.x]); + } +} diff --git a/Source/Serrano/operators/basic/binary/binary_op.swift b/Source/Serrano/operators/basic/binary/binary_op.swift new file mode 100644 index 0000000..7ab7c08 --- /dev/null +++ b/Source/Serrano/operators/basic/binary/binary_op.swift @@ -0,0 +1,658 @@ +// +// binary_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Dispatch +import Metal +import Accelerate + +/** + Abstract class define the standard binary operator working flow. + This class should not be used directly. + Any class inheritance this class is doing computation on exactly __two__ input tensors in element-wise way and return __one__ result tensor, i.e. + `x + y ->>> z` + This `BinaryOperator` does __not__ support broadcasting + */ +public class BinaryOperator: ComputableOperator { + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String = "" + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// The element compuation block in CPU mode. + /// In most cases, subclass should just override this part in `init` method instead overriding the whole `cpu(inputTensors:[Tensor], resultTensor: Tensor)` method. + /// The fisr tensor is input tensor A; + /// the seconds tensor is input tensor B; + /// the third tensor is output tensor C. + /// This block should do some computation and assign value back to result tensor's reader + public var cpuElementComputationBlock: (Tensor, Tensor, Tensor) -> Void + + + /// The grad compuation block. + /// parameter: inputA, inputB, mode + /// returns: An array of tensor. Should just have 2 object corresponding to two inputs + public var gradComputationBlock: (Tensor, Tensor, OperatorComputationMode) -> [Tensor] + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + /// + /// - Note: All `UnaryOperators` are not trainable. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `Constant` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.Constant + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init function + /// + /// - Parameters: + /// - label: label description + /// - delegate: delegate description + public init(operatorLabel label: String, + cpuComputeBlock block: @escaping (Tensor, Tensor, Tensor) -> Void , + gradComputationBlock gradBlock: @escaping (Tensor, Tensor, OperatorComputationMode) -> [Tensor], + metalKernelFuncLabel kernelLabel: String, + computationDelegate: OperatorCalculationDelegate?) { + self.operatorLabel = label + self.computationDelegate = computationDelegate + self.metalKernelFuncLabel = kernelLabel + self.cpuElementComputationBlock = block + self.gradComputationBlock = gradBlock + } + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + fatalError("NEED OVERRIDE") + } + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + fatalError("NEED OVERRIDE") + } + let defaultLabel = "NEED OVERRIDE" + let kernelLabel = "NEED OVERRIDE" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } + + + /// Convenience initializer + /// + /// - Parameters: + /// - computationDelegate: computationDelegate description + /// - inputTensors: inputTensors description + /// - outputTensors: outputTensors description + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, + inputTensors: [Tensor], outputTensors: [Tensor]) { + self.init(computationDelegate: computationDelegate) + self.inputTensors = inputTensors + self.outputTensors = outputTensors + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// This operator should just receive two tensors with same dimensions (dataType could be different). + /// Return shape is exactly the same shape as input. + /// + /// - Parameter shapes: input shapes + /// - Returns: return shapes + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + guard shapes.count == 2 else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) could just receive two input tensors. Given \(shapes.count)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // dimension size should be the same. Ignore type + guard shapes[0] == shapes[1] else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) receive two shapes not dimension equal. Given \(shapes[0].shapeArray) and \(shapes[1].shapeArray)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + return [shapes[0]] + } + + /// The `inputTensors` should have exactly two tensors and same dimensions. + /// The `outputTensors` should have exactly one tensora and same dimension with input tensors. + /// + /// + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // inpu tensor and output tensor not nil + guard self.inputTensors != nil && self.outputTensors != nil else { + return (false, "Operator \(self.operatorLabel) should non-nil inputTensors and outputTensors.") + } + + // input tensor shapes checck + let inputShapes = self.inputTensors!.map { $0.shape } + guard self.outputShape(shapeArray: inputShapes) != nil else { + return (false, "Operator \(self.operatorLabel) does not have valid input tensors.") + } + + // output tensors check + guard self.outputTensors!.count == 1 else { + return (false, "Operator \(self.operatorLabel) does not have valid number of output tensors. Require 1, given \(self.outputTensors!.count)") + } + + // input tensor and output tensor shape match checking + guard self.outputTensors![0].shape == inputShapes[0] else { + return (false, "Operator \(self.operatorLabel) does not have valid output tensor shapes. Require \(inputShapes[0]), given \(self.outputTensors![0].count)") + } + + return (true, "") + } + + /// Compute asynclly + /// + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: computation mode + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Compute synclly. + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: cmputation mode. If choose `GPU` but haven't configued a GPU SerranoEngine, operator will use `CPU` to compute. + /// - Returns: result tensors + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) calculation aborted cause invalid input tensors or output tensors: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + let grads = self.gradComputationBlock(self.inputTensors![0], self.inputTensors![1], computationMode) + var result = [String: DataSymbolSupportedDataType]() + for (i, grad) in grads.enumerated() { + result["input_\(i)"] = grad + } + return result + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + + /// Update params if possible. + /// No update parameters for binary operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// Binary operator has no parameters. Do nothing + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + /// Use cpu do the inplace computation. This function always do inPlace computation for inputTensorA. + /// It's caller function to decide the tensor's assignment. + /// Default, `UnaryOperator` defines a workflow. Subclass just needs to override `cpuElementComputationBlock`. + /// If subclass needs custom flow, it could just override this function. + /// + /// - Note: This function should not be called from outside. + /// + /// - Parameter tensors: the operation tensors + internal func cpu() { + self.cpuElementComputationBlock(self.inputTensors![0], self.inputTensors![1], self.outputTensors![0]) + } + + /// Let GPU call the Metal kernel to do the inplace computation.This function always do inPlace computation for inputTensorA. + /// It's caller function to decide the tensor's assignment. + /// Default, `UnaryOperator` defines a workflow. Subclass just needs to override `metalKernelFuncLabel` attribute. + /// If subclass needs custom flow, it could just override this function. + /// + /// - Note: This function should not be called from outside. + /// + /// - Parameter tensors: the operation tensors + internal func gpu() { + // prepare resources + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffer: MTLCommandBuffer? + var dataBuffers: [MTLBufferResource] = [MTLBufferResource]() + var countBuffer: MTLBuffer? + + // kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + // command buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + commandBuffer = engine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + resourcePrepareGroup.leave() + } + + //// Prepare MTLBuffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + dataBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources([self.inputTensors![0], self.inputTensors![1], self.outputTensors![0]]) + + resourcePrepareGroup.leave() + } + + resourcePrepareGroup.wait() + + + // dimensionBuffer + var count = UInt32(self.inputTensors![0].count) + countBuffer = engine.GPUDevice?.makeBuffer(bytes: &count, length: MemoryLayout.size) + guard countBuffer != nil else { fatalError("[Serrano] Failed to careate MTLBuffer.") } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + //// Prepare encoders. + let encoder = commandBuffer!.makeComputeCommandEncoder() + encoder.setComputePipelineState(kernel!) + encoder.setBuffer(dataBuffers[0].buffer, offset: dataBuffers[0].offset, at: 0) + encoder.setBuffer(dataBuffers[1].buffer, offset: dataBuffers[1].offset, at: 1) + encoder.setBuffer(dataBuffers[2].buffer, offset: dataBuffers[2].offset, at: 2) + encoder.setBuffer(countBuffer, offset: 0, at: 3) + + // dispatch + let threadsPerThreadgroup = MTLSizeMake(kernel!.threadExecutionWidth, + 1, + 1) + let threadgroupsPerGrid = MTLSizeMake((self.inputTensors![0].count + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, + 1, + 1) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + encoder.endEncoding() + + // commit command buffer + commandBuffer!.commit() + commandBuffer!.waitUntilCompleted() + } +} + +/** + Do `a+b`. Not support broadcasting. + */ +public class AddOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + let count = vDSP_Length(outputC.count) + vDSP_vadd(inputAAddress, 1, inputBAddress, 1, outputCAddress, 1, count) + } + + // dc/da = 1; dc/db = 1 + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + let gradA = Tensor(repeatingValue: 1.0, tensorShape: inputA.shape) + let gradB = Tensor(repeatingValue: 1.0, tensorShape: inputB.shape) + return [gradA, gradB] + } + + + let defaultLabel = "AddOperator" + let kernelLabel = "Add" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + +/** +Do `a-b`. Not support broadcasting. +*/ +public class SubOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + let count = vDSP_Length(outputC.count) + vDSP_vsub(inputBAddress, 1, inputAAddress, 1, outputCAddress, 1, count) + } + + // dc/da = 1; dc/db = -1 + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + let gradA = Tensor(repeatingValue: 1.0, tensorShape: inputA.shape) + let gradB = Tensor(repeatingValue: -1.0, tensorShape: inputB.shape) + return [gradA, gradB] + } + + let defaultLabel = "SubOperator" + let kernelLabel = "Sub" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + +/** +Do `a*b` in element-wise way. Not support broadcasting. +*/ +public class MultOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + let count = vDSP_Length(outputC.count) + vDSP_vmul(inputAAddress, 1, inputBAddress, 1, outputCAddress, 1, count) + } + + // dc/da = b; dc/db = a + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + /// First allocate as managed to speed up incase using GPU with reusing MTLBuffers + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors([inputA.shape, inputB.shape]) + let copyOp = CopyOperator(inputTensors: [inputA, inputB], outputTensors: [grads[1], grads[0]]) + copyOp.disableInputOutputCheck = true + copyOp.compute(mode) + return grads + } + + let defaultLabel = "MultOperator" + let kernelLabel = "Mult" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + +/** +Do `a/b` in element-wise way. Not support broadcasting. +*/ +public class DivOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + let count = vDSP_Length(outputC.count) + vDSP_vdiv(inputBAddress, 1, inputAAddress, 1, outputCAddress, 1, count) + } + + // dc/da = 1/b; dc/db = -a/b^2 + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + let workGroup = DispatchGroup() + + // A + var gradA: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + gradA = 1.0 / inputB + workGroup.leave() + } + + // B + var gradB: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + // a / b^2 + gradB = inputB * inputB + let divOp = DivOperator(inputTensors: [inputA, gradB!], outputTensors: [gradB!]) + divOp.disableInputOutputCheck = true + divOp.compute(mode) + // -1.0 + -1.0 &* gradB! + workGroup.leave() + } + workGroup.wait() + return [gradA!, gradB!] + } + + let defaultLabel = "DivOperator" + let kernelLabel = "Div" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + + +/** +Do `b/a` in element-wise way. Not support broadcasting. +*/ +public class RDivOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + let count = vDSP_Length(outputC.count) + vDSP_vdiv(inputAAddress, 1, inputBAddress, 1, outputCAddress, 1, count) + } + + // dc/da = -b/a^2; dc/db = 1/a + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + let workGroup = DispatchGroup() + + // A + var gradA: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + // b / a^2 + gradA = inputA * inputA + let divOp = DivOperator(inputTensors: [inputB, gradA!], outputTensors: [gradA!]) + divOp.disableInputOutputCheck = true + divOp.compute(mode) + // -1.0 + -1.0 &* gradA! + workGroup.leave() + } + + // B + var gradB: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + gradB = 1.0 / inputA + workGroup.leave() + } + + workGroup.wait() + return [gradA!, gradB!] + } + + + let defaultLabel = "RDivOperator" + let kernelLabel = "RDiv" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + +/** +Do `a^b` in element-wise way. Not support broadcasting. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. +*/ +public class PowOperator: BinaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputA: Tensor, inputB: Tensor, outputC: Tensor) -> Void in + let inputAAddress = inputA.contentsAddress + let inputBAddress = inputB.contentsAddress + let outputCAddress = outputC.contentsAddress + var count = Int32(outputC.count) + vvpowf(outputCAddress, inputBAddress, inputAAddress, &count) + } + + // dc/da = b * a^(b-1); dc/db = (a^b) * ln(a) + let gradBlock = { (inputA: Tensor, inputB: Tensor, mode: OperatorComputationMode) -> [Tensor] in + let workGroup = DispatchGroup() + + // A + var gradA: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + gradA = SerranoResourceManager.globalManager.allocateUnamangedTensor(inputA.shape) + let bm1 = inputB - 1.0 + // a^(b-1) + let powOp = PowOperator(inputTensors: [inputA, bm1], outputTensors: [gradA!]) + powOp.disableInputOutputCheck = true + powOp.compute(mode) + // * b + gradA! &* inputB + workGroup.leave() + } + + // B + var gradB: Tensor? + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + gradB = SerranoResourceManager.globalManager.allocateUnamangedTensor(inputB.shape) + // lna + let group = DispatchGroup() + let lna = Tensor(repeatingValue: 0.0, tensorShape: inputA.shape) + DispatchQueue.global(qos: .userInitiated).async { + group.enter() + let logOp = LogOperator(inputTensors: [inputA], outputTensors: [lna]) + logOp.disableInputOutputCheck = true + logOp.compute(mode) + group.leave() + } + + // (a^b) + let powOp = PowOperator(inputTensors: [inputA, inputB], outputTensors: [gradB!]) + powOp.disableInputOutputCheck = true + powOp.compute(mode) + + // * + group.wait() + gradB! &* lna + + workGroup.leave() + } + + workGroup.wait() + return [gradA!, gradB!] + } + + let defaultLabel = "PowOperator" + let kernelLabel = "Pow" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate) + } +} + diff --git a/Source/Serrano/operators/basic/broadcast/broadcast_arithmetic_ops.swift b/Source/Serrano/operators/basic/broadcast/broadcast_arithmetic_ops.swift new file mode 100644 index 0000000..780fa9e --- /dev/null +++ b/Source/Serrano/operators/basic/broadcast/broadcast_arithmetic_ops.swift @@ -0,0 +1,424 @@ +// +// tensor_arithmetic_ops.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/4/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/** +The abstract parent class for all broadcast arithmetic oeprators. +Any child class of this operator support element-wise calculation between two `Tensor` objects with broadcasting support. +*/ +public class BroadcastArithmeticOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + public var computationDelegate: OperatorCalculationDelegate? + + public var metalKernelFuncLabel: String + + public var operatorLabel: String + + public var inputTensors: [Tensor]? + + public var outputTensors: [Tensor]? + + /// Computation logic block. + /// First param is input tensors, seconds param is output tensors. + /// Operator just needs override this in requried init function instead of override whole computation methods. + /// All input tensors are already broadcasted. + public lazy var calculationBlock: ([Tensor], [Tensor], OperatorComputationMode) -> Void = {_,_,_ in } + + /// The grad compuation block. + /// parameter: inputA, inputB, + /// returns: An array of tensor. Should just have 2 object corresponding to two inputs + public var gradComputationBlock: (Tensor, Tensor, OperatorComputationMode) -> [Tensor] = {(_,_,_) -> [Tensor] in + fatalError("Not implemented") + } + + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + /// + /// - Note: `BroadcastArithmeticOperator` is not trainable. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + + /// Initializer. + /// + /// - Note: In most cases this initializer should not be called directly. + /// call one of the convenience initializers instead. + /// + /// - Parameters: + /// - computationDelegate: computationDelegate + /// - inputTensors: inputTensors + /// - outputTensors: outputTensors + /// - metalKernelFuncLabel: metalKernelFuncLabel + /// - operatorLabel: operatorLabel + public init(computationDelegate: OperatorCalculationDelegate?, + inputTensors: [Tensor]?, outputTensors: [Tensor]?, + metalKernelFuncLabel: String, + operatorLabel: String) { + self.computationDelegate = computationDelegate + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.metalKernelFuncLabel = metalKernelFuncLabel + self.operatorLabel = operatorLabel + } + + /// Conevnience initializer. + /// + /// - Note: All subclass should override this convenience initializer to setup `metalKernelFuncLabel` and default `operatorLabel`. + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + // no need metal. Just use other existing operators + let metalKernelFuncLabel = "" + let defaultLable = "OVERRIDE" + self.init(computationDelegate: computationDelegate, inputTensors: nil, outputTensors: nil, + metalKernelFuncLabel: metalKernelFuncLabel, operatorLabel: defaultLable) + } + + /// Conenience initializer. + /// + /// - Parameters: + /// - computationDelegate: computationDelegate + /// - inputTensors: inputTensors + /// - outputTensors: outputTensors + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, inputTensors: [Tensor], outputTensors: [Tensor]) { + self.init(computationDelegate: computationDelegate) + self.inputTensors = inputTensors + self.outputTensors = outputTensors + } + + + /// Convenience initializer + /// + /// - Parameters: + /// - computationDelegate: computationDelegate + /// - operatorLabel: operatorLabel + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, operatorLabel: String) { + self.init(computationDelegate: computationDelegate) + self.operatorLabel = operatorLabel + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + + /// Check the input shapes. + /// Should exactly contains two shapes and the two shapes should be have same dimensions with or without broadcasting. + /// + /// - Parameter shapes: input shapes + /// - Returns: output shapes, maybe `nil` if not valid + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // two input shapes + guard shapes.count == 2 else { + SerranoLogging.errorLogging(message: "Input shapes should have exactly 2 shapes. Given \(shapes.count)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // shape match + let shapeA = shapes[0] + let shapeB = shapes[1] + if shapeA == shapeB { + // same dim + return [shapeA] + } else { + // if not same dims, check if could broadcast to higher rank shape + let broadcastOp = BroadcastOperator(targetShape: max(shapeA, shapeB)) + if shapeA < shapeB { + return broadcastOp.outputShape(shapeArray: [shapeA]) + } else { + return broadcastOp.outputShape(shapeArray: [shapeB]) + } + } + } + + + /// Check validation of shapes mathcing between `inpuTensors` and `outputTensors`. + /// + /// - Returns: `check` indicates if validation, `msg` containing error information. + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input not nil + guard self.inputTensors != nil else { + return (false, "Input tensors are nil") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Output tensors are nil") + } + + // check input shapes + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapeCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapeCheck != nil else { + return (false, "Invalid shapes from input tensors. Check log for details.") + } + + // check output shape match + let outputShapes = self.outputTensors!.map { $0.shape } + guard outputShapeCheck!.count == outputShapes.count else { + return (false, "Ouput tensors amount is not valid. Expect \(outputShapeCheck!.count), given \(outputShapes.count).") + } + for (shape, shapeCheck) in zip(outputShapes, outputShapeCheck!) { + guard shape == shapeCheck else { + return (false, "Invalid shape in output tensors. Expect \(shapeCheck.description), given \(shape.description)") + } + } + + return (true, "") + } + + + /// Usually, a `BroadcastArithmeticOperator` just call `BroadcastOperator` and other calculation operators. + /// This methods will first do braodcast on input tensors if needs and then call `calculationBlock`. + /// - Parameter computationMode: computationMode + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: msg, file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + var inputA: Tensor = self.self.inputTensors![0] + var inputB: Tensor = self.self.inputTensors![1] + + // Do broadcast if needs + let shapeA = self.inputTensors![0].shape + let shapeB = self.inputTensors![1].shape + if shapeA != shapeB { + let broadcastOp = BroadcastOperator(targetShape: max(shapeA, shapeB)) + if shapeA < shapeB { + // broadcast A + broadcastOp.inputTensors = [self.inputTensors![0]] + inputA = SerranoResourceManager.globalManager.allocateTensor(shapeB) + broadcastOp.outputTensors = [inputA] + } else if shapeA > shapeB { + // broadcast B + broadcastOp.inputTensors = [self.inputTensors![1]] + inputB = SerranoResourceManager.globalManager.allocateTensor(shapeA) + broadcastOp.outputTensors = [inputB] + } + broadcastOp.compute(computationMode) + } + + // call calculation block + self.calculationBlock([inputA, inputB], self.outputTensors!, computationMode) + + // return intermediate tensors + if inputA != self.inputTensors![0] { SerranoResourceManager.globalManager.returnTensor(inputA) } + if inputB != self.inputTensors![1] { SerranoResourceManager.globalManager.returnTensor(inputB) } + + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + let grads = self.gradComputationBlock(self.inputTensors![0], self.inputTensors![1], computationMode) + var result = [String: DataSymbolSupportedDataType]() + for (i, grad) in grads.enumerated() { + result["input_\(i)"] = grad + } + return result + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + + /// Update params if possible. + /// No update parameters for broadcast arithmetic operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } +} + +/** +Broadcasting addition. + +- Note: The `inputTensors` of this operator should just have exactly 2 tensors and the `outputTensors` should just have 1 tensor. +*/ +public class BroadcastAddOperator: BroadcastArithmeticOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Override requred inializer + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let metalKernelFuncLabel = "" + let defaultLable = "BroadcastAddOp" + self.init(computationDelegate: computationDelegate, inputTensors: nil, outputTensors: nil, + metalKernelFuncLabel: metalKernelFuncLabel, operatorLabel: defaultLable) + self.calculationBlock = { (inputTensors: [Tensor], outputTensors: [Tensor], computationMode: OperatorComputationMode) -> Void in + let inputA = inputTensors[0] + let inputB = inputTensors[1] + let output = outputTensors[0] + // element wise add + let addOp = AddOperator(inputTensors: [inputA, inputB], outputTensors: [output]) + addOp.compute(computationMode) + } + + //TODO: Implement gradBlock + } +} + +/** +Broadcasting substraction. + +- Note: The `inputTensors` of this operator should just have exactly 2 tensors and the `outputTensors` should just have 1 tensor. +*/ +public class BroadcastSubOperator: BroadcastArithmeticOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Override requred inializer + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let metalKernelFuncLabel = "" + let defaultLable = "BroadcastSubOp" + self.init(computationDelegate: computationDelegate, inputTensors: nil, outputTensors: nil, + metalKernelFuncLabel: metalKernelFuncLabel, operatorLabel: defaultLable) + self.calculationBlock = { (inputTensors: [Tensor], outputTensors: [Tensor], computationMode: OperatorComputationMode) -> Void in + let inputA = inputTensors[0] + let inputB = inputTensors[1] + let output = outputTensors[0] + // element wise add + let subOp = SubOperator(inputTensors: [inputA, inputB], outputTensors: [output]) + subOp.compute(computationMode) + } + + //TODO: Implement gradBlock + } +} + +/** +Broadcasting multiplication. + +- Note: The `inputTensors` of this operator should just have exactly 2 tensors and the `outputTensors` should just have 1 tensor. +*/ +public class BroadcastMultOperator: BroadcastArithmeticOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Override requred inializer + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let metalKernelFuncLabel = "" + let defaultLable = "BroadcastMultOp" + self.init(computationDelegate: computationDelegate, inputTensors: nil, outputTensors: nil, + metalKernelFuncLabel: metalKernelFuncLabel, operatorLabel: defaultLable) + self.calculationBlock = { (inputTensors: [Tensor], outputTensors: [Tensor], computationMode: OperatorComputationMode) -> Void in + let inputA = inputTensors[0] + let inputB = inputTensors[1] + let output = outputTensors[0] + // element wise add + let multOp = MultOperator(inputTensors: [inputA, inputB], outputTensors: [output]) + multOp.compute(computationMode) + } + + //TODO: Implement gradBlock + } +} + +/** +Broadcasting division. + +- Note: The `inputTensors` of this operator should just have exactly 2 tensors and the `outputTensors` should just have 1 tensor. +*/ +public class BroadcastDivOperator: BroadcastArithmeticOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Override requred inializer + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let metalKernelFuncLabel = "" + let defaultLable = "BroadcastDivOp" + self.init(computationDelegate: computationDelegate, inputTensors: nil, outputTensors: nil, + metalKernelFuncLabel: metalKernelFuncLabel, operatorLabel: defaultLable) + self.calculationBlock = { (inputTensors: [Tensor], outputTensors: [Tensor], computationMode: OperatorComputationMode) -> Void in + let inputA = inputTensors[0] + let inputB = inputTensors[1] + let output = outputTensors[0] + // element wise add + let divOp = DivOperator(inputTensors: [inputA, inputB], outputTensors: [output]) + divOp.compute(computationMode) + } + + //TODO: Implement gradBlock + } +} diff --git a/Source/Serrano/operators/basic/broadcast/broadcast_op.swift b/Source/Serrano/operators/basic/broadcast/broadcast_op.swift new file mode 100644 index 0000000..fd3cd6d --- /dev/null +++ b/Source/Serrano/operators/basic/broadcast/broadcast_op.swift @@ -0,0 +1,331 @@ +// +// broadcast_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + +/** +Doing broadcasting on input tensors and store result in output tensors. +Serrano follows the broadcasting rule of [Scipy](http://scipy.github.io/old-wiki/pages/EricsBroadcastingDoc). +*/ +public class BroadcastOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String = "" + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// Target shape. + /// - Note: This atrribute could be `nil` when initialize an object. + /// If it is `nil` doing calculation, a `fatalError()` will be raise. + public var targetShape: TensorShape? + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + /// + /// - Note: `BroadcastOperator` is not trainable. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Initializers + /// + /// - Note: In most cases, this initializer should not be called. + /// Call one of the convenience initializers instead. + /// + /// - Parameters: + /// - label: + /// - kernelLabel: + /// - computationDelegate: + /// - inputTensors: + /// - outputTensors: + /// - targetShape: + public init(operatorLabel label: String, + metalKernelFuncLabel kernelLabel: String, + computationDelegate: OperatorCalculationDelegate?, + inputTensors: [Tensor]?, + outputTensors: [Tensor]?, + targetShape: TensorShape?) { + self.operatorLabel = label + self.computationDelegate = computationDelegate + self.metalKernelFuncLabel = kernelLabel + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.targetShape = targetShape + } + + + /// + /// + /// - Parameters: + /// - computationDelegate: + /// - targetShape: + public convenience init(computationDelegate: OperatorCalculationDelegate, targetShape: TensorShape?) { + let defaultLabel = "BroadCastOp" + let kernelLabel = "" + self.init(operatorLabel: defaultLabel, metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil, targetShape: targetShape) + } + + + /// Initializer + /// + /// - Parameter targetShape: target broadcast shape + public convenience init(targetShape: TensorShape? = nil) { + let defaultLabel = "BroadCastOp" + let kernelLabel = "" + self.init(operatorLabel: defaultLabel, metalKernelFuncLabel: kernelLabel, computationDelegate: nil, + inputTensors: nil, outputTensors: nil, targetShape: targetShape) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Conforms to `ComputableOperator` + + /// The input shapes and target shape should follow the [`scipy rule`](http://scipy.github.io/old-wiki/pages/EricsBroadcastingDoc) + /// + /// - Parameter shapes: input shapes + /// - Returns: return shapes. `nil` if not valid + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // target shape set + guard self.targetShape != nil else { + SerranoLogging.errorLogging(message: "Did not setup targetShape.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + let targetShapeReversed = Array(self.targetShape!.shapeArray.reversed()) + + let transformedShape = shapes.flatMap { tensorShape -> TensorShape? in + // dimensions check + guard tensorShape.shapeArray.count > 0 && tensorShape.shapeArray.count <= self.targetShape!.shapeArray.count else { + SerranoLogging.errorLogging(message: "Invalid shape: \(tensorShape.shapeArray) for target shape: \(self.targetShape!.shapeArray)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // dim size check + let tensorShapeReversed = Array(tensorShape.shapeArray.reversed()) + for (dimInput, dimTarget) in zip(tensorShapeReversed, targetShapeReversed) { + guard dimInput == dimTarget || dimInput == 1 else { + SerranoLogging.errorLogging(message: "Invalid shape: \(tensorShape.shapeArray) for target shape: \(self.targetShape!.shapeArray)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + } + + return self.targetShape + } + + if transformedShape.count != shapes.count { + return nil + } else { + return transformedShape + } + } + + + /// Check if shapes of all input tensors and output tensors are compatible with `targetShape` + /// + /// - Returns: check and message + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + guard self.inputTensors != nil else { + return (false, "Input tensors are nil") + } + + guard self.outputTensors != nil else { + return (false, "Output tensors are nil") + } + + guard self.inputTensors!.count == self.outputTensors!.count else { + return (false, "Input and output tensors should have same number of tensors. Given input: \(self.inputTensors!.count) and output: \(self.outputTensors!.count)") + } + + // check each tensor's shape + for tensor in self.inputTensors! { + guard self.outputShape(shapeArray: [tensor.shape]) != nil else { + return (false, "Input tensor \(tensor.description) cannot be broadcast to target shape \(self.targetShape!.description)") + } + } + + // check each tensor's shape + for tensor in self.outputTensors! { + guard self.outputShape(shapeArray: [tensor.shape]) != nil else { + return (false, "Output tensor \(tensor.description) cannot be broadcast to target shape \(self.targetShape!.description)") + } + } + + return (true, "") + } + + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) calculation aborted cause invalid input tensors or output tensors: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + self.cpu() + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// Broadcast operator itself does not generate any grads. Should be ignored in gaph AD. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return `upGrads` if not nil. Else return an empty array. + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + return [:] + } + + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + + + /// No updatable parameters. + /// This function just returns. + /// + /// - Parameters: + /// - grads: grads + /// - LR: LR + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + public func cpu() { + for (tensor, targetTensor) in zip(self.inputTensors!, self.outputTensors!) { + + // get reverse shapes for convenience + let targetShapeReversed = Array(self.targetShape!.shapeArray.reversed()) + var rawShapeReversed = Array(tensor.shape.shapeArray.reversed()) + + for i in 0..= rawShapeReversed.count { + rawShapeReversed.append(0) + } + let rawDim = rawShapeReversed[i] + let targetDim = targetShapeReversed[i] + if i == 0 { // 1st dim + if rawDim == targetDim { // just copy each element + let cpFromAddress = tensor.contentsAddress + let cpToAddress = UnsafeMutableRawPointer(targetTensor.contentsAddress) + memcpy(cpToAddress, cpFromAddress, tensor.count * MemoryLayout.stride) + } else { // copy each element for targetDim times + let reader = tensor.floatValueReader + let writer = targetTensor.floatValueReader + var writerIndex = 0 + for elementCount in 0...stride + + // Needs to move first avoid over writing + for i in 0...stride + let cpFromAddress = UnsafeRawPointer(targetTensor.contentsAddress) + var cpToAddress = UnsafeMutableRawPointer(targetTensor.contentsAddress) + blockSize + for _ in 1.. +using namespace metal; + +namespace serrano_ios { + + typedef struct + { + uint M; // # of row in transposed matrix + uint N; // # of cols in transposed matrix + ushort stride; // stride of element + } TransposeMatrixInfo; + + kernel void Transpose(const device float* in_tensor [[ buffer(0) ]], + const device float* out_tensor [[ buffer(1) ]], + constant TransposeMatrixInfo& matrix_info [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) { + uint M = matrix_info.M; + uint N = matrix_info.N; + ushort stride = matrix_info.stride; + + if (gid.x >= M || gid.y >= N) return; + + const device float* src = (const device float*)((device char*)in_tensor + (M * gid.y + gid.x) * stride); + device float* out = (device float*)((device char*)out_tensor + (N * gid.x + gid.y) * stride); + out[0] = src[0]; + } +} diff --git a/Source/Serrano/operators/basic/broadcast/transpose_op.swift b/Source/Serrano/operators/basic/broadcast/transpose_op.swift new file mode 100644 index 0000000..1bd2022 --- /dev/null +++ b/Source/Serrano/operators/basic/broadcast/transpose_op.swift @@ -0,0 +1,343 @@ +// +// transpose_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Accelerate + +public struct TransposeMatrixInfo { + var M: MetalUInt + var N: MetalUInt + var stride: MetalUShort +} + +/** +Transpose 2D matrix on all `inputTensors` and put transposed values in `outputTensors`. +*/ +public class TransposeOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var metalKernelFuncLabel: String = "Transpose" + + /// Conforms to `ComputableOperator` + public var operatorLabel: String + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + public init(inputTensors: [Tensor]?, outputTensors: [Tensor]?, + computationDelegate: OperatorCalculationDelegate?, operatorLabel: String) { + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.computationDelegate = computationDelegate + self.operatorLabel = operatorLabel + } + + public convenience init(computationDelegate: OperatorCalculationDelegate?) { + self.init(inputTensors: nil, outputTensors: nil, computationDelegate: computationDelegate, operatorLabel: "TransposeOp") + } + + public convenience init(label: String = "TransposeOp") { + self.init(inputTensors: nil, outputTensors: nil, computationDelegate: nil, operatorLabel: label) + } + + public convenience init(inputTensors: [Tensor], outputTensors: [Tensor]) { + self.init(inputTensors: inputTensors, outputTensors: outputTensors, computationDelegate: nil, operatorLabel: "TransposeOp") + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Conforms to `ComputableOperator` + + + /// Only accepts 2D matrix. + /// + /// - Parameter shapes: input shapes + /// - Returns: output shapes + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + guard shapes.count != 0 else { + SerranoLogging.errorLogging(message: "Input shapes contains no shape.", + file: "\(#file)", function: "#\(#function)", line: "\(#line)") + return nil + } + + var returnShapes = [TensorShape]() + for shape in shapes { + guard shape.rank == 2 else { + SerranoLogging.errorLogging(message: "Shape \(shape) should has rank values as 2, given \(shape.rank)", + file: "\(#file)", function: "#\(#function)", line: "\(#line)") + return nil + } + returnShapes.append(TensorShape(dataType: shape.dataType, shape: [shape.shapeArray[1], shape.shapeArray[0]])) + } + + return returnShapes + } + + + /// Check if `inputTensors` and `outputTensors` have correct corresponding tensors. + /// + /// - Returns: passing and message + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input tensors not nil + guard self.inputTensors != nil else { + return (false, "Input tensors are nil") + } + + // output tensors not nil + guard self.outputTensors != nil else { + return (false, "Output tensors are nil") + } + + // same count + guard self.inputTensors!.count == self.outputTensors!.count else { + return (false, "Input tensors and output tensors have different number of elements. Input tensors: \(self.inputTensors!.count). Output tensors: \(self.outputTensors!.count).") + } + + // input shapes check + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapesCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapesCheck != nil else { + return (false, "Input tensors' shapes are not valid. Check log for details.") + } + + // comapre shapes + let outputShapes = self.outputTensors!.map { $0.shape } + for shapeIndex in 0.. 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + //TODO: Implementation + fatalError("Not implemented") + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + _ = self.gradCompute(computationMode) + } + } + + /// Update params if possible. + /// No update parameters for binary operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + internal func cpu() { + for i in 0...stride)) + let matrixInfoBuffer = engine.GPUDevice?.makeBuffer(bytes: &matrixInfo, + length: MemoryLayout.size) + guard matrixInfoBuffer != nil else { + SerranoLogging.errorLogging(message: "Failed to create matrix Info Buffer", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(matrixInfoBuffer!.length) bytes] requested for count matrixInfo \(matrixInfo) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + matrixInfoBuffers.append(matrixInfoBuffer!) + } + resourcePrepareGroup.leave() + } + + resourcePrepareGroup.wait() + + // encoder for each computation + for bufferIndex in 0.. +using namespace metal; + + +typedef struct +{ + uint M; // number of rows in A + uint N; // number of cols in B + uint K; // number of cols in A, number of rows in B + ushort stride; // Element stride in bytes +} MatrixDimInfo; + +kernel void MatrixMult_Single(constant float* input_A [[ buffer(0) ]], + constant float* input_B [[ buffer(1) ]], // should be transposed + const device float* C [[ buffer(2) ]], + constant MatrixDimInfo& dims [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) { + uint M = dims.M; + uint N = dims.N; + uint K = dims.K; + ushort stride = dims.stride; + // check boundary + if (gid.x >= M || gid.y >= N) return; + + device float* c_reader = (device float*)((device char*)C + gid.x * N * stride + gid.y * stride); + c_reader[0] = 0.0f; + + // small case + if (K < 4) { + constant float* a_reader = (constant float*)((constant char*)input_A + gid.x * K * stride); + constant float* b_reader = (constant float*)((constant char*)input_B + gid.y * K * stride); + ushort i = 0; + while (i < K) { + c_reader[0] += a_reader[i] * b_reader[i]; + i++; + } + return; + } + + // regular case, each time read 4 elements + constant float4* a_reader = (constant float4*)((constant char*)input_A + gid.x * K * stride); + constant float4* b_reader = (constant float4*)((constant char*)input_B + gid.y * K * stride); + uint align_bound = K / 4; + ushort i = 0; + float4 result; + while (i < align_bound) { + result = a_reader[i] * b_reader[i]; + c_reader[0] += result.x; + c_reader[0] += result.y; + c_reader[0] += result.z; + c_reader[0] += result.w; + i++; + } + + // rest + if (K % 4 != 0) { + constant float* a_reader_p = (constant float*)((constant char*)input_A + gid.x * K * stride); + constant float* b_reader_p = (constant float*)((constant char*)input_B + gid.y * K * stride); + i = align_bound * 4; + while (i < K) { + c_reader[0] += a_reader_p[i] * b_reader_p[i]; + i++; + } + } +} + + +kernel void MatrixMult_submatrix(constant float* input_A [[ buffer(0) ]], // should be transposed for convenience + constant float* input_B [[ buffer(1) ]], + const device float* C [[ buffer(2) ]], + constant MatrixDimInfo& dims [[ buffer(3) ]], + uint2 gid [[ thread_position_in_grid ]]) { + uint M = dims.M; + uint N = dims.N; + uint K = dims.K; + ushort stride = dims.stride; + + const ushort SUBMATRIX_SIZE = 4; + + // output element start position + uint2 gid_out = uint2(gid.x * SUBMATRIX_SIZE, gid.y * SUBMATRIX_SIZE); // times 4 + + // check boundary + if (gid_out.x >= M || gid_out.y >= N) return; + + ushort row_bound = min(gid_out.x + SUBMATRIX_SIZE, M) - gid_out.x; + ushort col_bound = min(gid_out.y + SUBMATRIX_SIZE, N) - gid_out.y; + + float4x4 output_c_m = float4x4(0.0f); + constant float4* a_reader = (constant float4*)((constant char*)input_A + gid_out.x * stride); + constant float4* b_reader = (constant float4*)((constant char*)input_B + gid_out.y * stride); + for (uint i = 0; i < K; i++) { // loop block + output_c_m[0] += a_reader[0].x * b_reader[0]; + output_c_m[1] += a_reader[0].y * b_reader[0]; + output_c_m[2] += a_reader[0].z * b_reader[0]; + output_c_m[3] += a_reader[0].w * b_reader[0]; + + a_reader = (constant float4*)((constant char*)a_reader + M * stride); + b_reader = (constant float4*)((constant char*)b_reader + N * stride); + } + + + // assign + device float* c_reader = (device float*)((device char*)C + gid_out.x * N * stride + gid_out.y * stride); + // reset boundary checker + for (int out_row_index = 0; out_row_index < row_bound; out_row_index++) { + for (int out_col_index = 0; out_col_index < col_bound; out_col_index++) { +// c_reader[out_col_index] = output_c[out_row_index*SUBMATRIX_SIZE + out_col_index]; + c_reader[out_col_index] = output_c_m[out_row_index][out_col_index]; + } + // skip row + c_reader = (device float*)((device char*)c_reader + N * stride); + } +} + diff --git a/Source/Serrano/operators/basic/matrix_mult_op.swift b/Source/Serrano/operators/basic/matrix_mult_op.swift new file mode 100644 index 0000000..f806b66 --- /dev/null +++ b/Source/Serrano/operators/basic/matrix_mult_op.swift @@ -0,0 +1,606 @@ +// +// dot_product_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Accelerate +import Metal +#if !((arch(i386) || arch(x86_64)) && os(iOS)) // prevent build error on simulator +import MetalPerformanceShaders +#endif + +/// This struct corresponds to the `MatrixDimInfo` struct in file 'matrix_mult_op.metal' +public struct MatrixDimInfo { + var M: MetalUInt // number of rows in A + var N: MetalUInt // number of cols in B + var K: MetalUInt // number of cols in A, number of rows in B + var stride: MetalUShort // element stride in bytes +} + + +/// Two kernels +/// +/// - Single: Caclulate a single element each thread +/// - SubMatrix: Caculate a submatrix each thread +public enum MatrixMultKernel { + case Single + case SubMatrix +} + + +/** +matrix multiplication. + +## Transpose input tensors +Opertor's attributes `transposeA` and `transposeB` indicating if tranposing input tensors before doing calculation. +And if any or both of them are set to `true`, all caulcation and input/output validation will be doing __after__ transposing. + +## Metal performance shader support +By default, operator tries to use [MPSMatrixMultiplication](https://developer.apple.com/documentation/metalperformanceshaders/mpsmatrixmultiplication) in `MetalPerformanceShaders`. +But on some devices which [do not support `MetalPerformanceShaders`](https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf), we use self kernel. + +## Multiple input +This operator could takein multiple input. Currently, it support multiple input `A` and single input `B`. +If `inputTensors` contains more than 2 elements, operator will view last element as input `B` and all previous element as input `A`s. + +*/ +public class MatrixMultOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// The submatrix size in Metal calcualtion. + /// Should be the same as `SUBMATRIX_SIZE` in `matrix_mult_op.metal` + public static let METAL_SUBMATRIX_SIZE = 4 + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String = "MatrixMult" + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// Whether transpose inputA before calculation + public var transposeA: Bool + + /// Whether transpose inputB before calcualtion + public var transposeB: Bool + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = false + + /// Kernel to choose + public var kernel: MatrixMultKernel + + /// The mapping type of this operator. + /// `Constant` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.Constant + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + public init(operatorLabel: String = "MatrixMultOperator", + computationDelegate: OperatorCalculationDelegate? = nil, + transposeA: Bool = false, transposeB: Bool = false, + inputTensors: [Tensor]? = nil, outputTensors: [Tensor]? = nil, + kernel: MatrixMultKernel = MatrixMultKernel.Single, + disableInputOutputCheck: Bool = false) { + self.operatorLabel = operatorLabel + self.computationDelegate = computationDelegate + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.transposeA = transposeA + self.transposeB = transposeB + self.kernel = kernel + self.disableInputOutputCheck = disableInputOutputCheck + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Conforms to `ComputableOperator` + + /// Check the input shapes. Following same rule of matrix multiplication. + /// + /// - Note: This function will transpose shape first and then calcualte output shape. + /// + /// - Parameter shapes: input shapes + /// - Returns: return shapes + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // >=2 shapes + guard shapes.count >= 2 else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) expect more than 2 shapes, given \(shapes.count)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + var shapeAs = shapes[0.. (check: Bool, msg: String) { + // not nil inputs + guard self.inputTensors != nil else { + return (false, "Input tensors are nil") + } + + // >=2 input tensors + guard self.inputTensors!.count >= 2 else { + return (false, "Invalid input tneosr. Requires more than 2, given \(self.inputTensors!.count)") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Output tensors are nil") + } + + // output tensors count match + guard self.outputTensors!.count == self.inputTensors!.count - 1 else { + return (false, "Invalid number of output tensors. Requires \(self.inputTensors!.count - 1), given \(self.inputTensors!.count)") + } + + // check dimension + let inputShapes = self.inputTensors!.map { $0.shape } + let checkResult = self.outputShape(shapeArray: inputShapes) + guard checkResult != nil else { + return (false, "Invalid input tneosrs. See logs for details.") + } + + // outptu dimension + let outputShapes = self.outputTensors!.map {$0.shape} + for (outShape, checkShape) in zip(outputShapes, checkResult!) { + guard outShape.shapeArray == checkShape.shapeArray else { + return (false, "Invalid output tensor. Expecting shape \(checkShape.shapeArray), given \(outShape.shapeArray)") + } + } + + return (true, "") + } + + + /// Compute sync + /// + /// - Parameter computationMode: mode + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + if self.disableInputOutputCheck { + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) calculation aborted cause invalid input tensors or output tensors: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + if self.outputTensors![0].count > 4000000 && SerranoEngine.configuredEngine.hasAvailableGPU() { + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + + /// Compute async + /// + /// - Parameter computationMode: mode + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // delegation check + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + //TODO: Implementation + fatalError("Not implemented") + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + _ = self.gradCompute(computationMode) + } + } + + /// Update params if possible. + /// No update parameters for binary operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// Do nothing + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + // DO NOTHING + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + + /// Get M, N, K attributes + /// + /// - Returns: M, N, K + internal func MNKFetch() -> (M: Int, N: Int, K: Int) { + let tensorA = self.inputTensors![0] + let tensorB = self.inputTensors![1] + + var M = tensorA.shape.shapeArray[0] + if self.transposeA { + M = tensorA.shape.shapeArray[1] + } + var N = tensorB.shape.shapeArray[1] + if self.transposeB { + N = tensorB.shape.shapeArray[0] + } + var K = tensorA.shape.shapeArray[1] + if self.transposeA { + K = tensorA.shape.shapeArray[0] + } + return (M, N, K) + } + + /// Get M, N, K attributes + /// + /// - Returns: M, N, K + internal func MNKFetch(tensorA: Tensor, tensorB: Tensor) -> (M: Int, N: Int, K: Int) { + var M = tensorA.shape.shapeArray[0] + if self.transposeA { + M = tensorA.shape.shapeArray[1] + } + var N = tensorB.shape.shapeArray[1] + if self.transposeB { + N = tensorB.shape.shapeArray[0] + } + var K = tensorA.shape.shapeArray[1] + if self.transposeA { + K = tensorA.shape.shapeArray[0] + } + return (M, N, K) + } + + /// Use `BLAS` `cblas_sgemm` to do calculation + internal func cpu() { + let workGroup = DispatchGroup() + let inputB = self.inputTensors!.last! + let inputBAddress = inputB.contentsAddress + + for (input, output) in zip(self.inputTensors!, self.outputTensors!) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + let inputAAddress = input.contentsAddress + let outCAddress = output.contentsAddress + + let (M, N, K) = self.MNKFetch(tensorA: input, tensorB: inputB) + + let lda = Int32(input.shape.shapeArray[1]) + let ldb = Int32(inputB.shape.shapeArray[1]) + let ldc = Int32(output.shape.shapeArray[1]) + + cblas_sgemm(CblasRowMajor, cblasTrans(self.transposeA), cblasTrans(self.transposeB), Int32(M), Int32(N), Int32(K), + 1.0, inputAAddress, lda, inputBAddress, ldb, 0.0, outCAddress, ldc) + workGroup.leave() + } + } + workGroup.wait() + } + + + /// This method choose proper kernel or MPS to do calculation + internal func gpu() { + // Use MPS if possible + if MetalHardwareChecker.supportMPS() { + self.gpu_kernel_MPS() + return + } + // choose kernel + if self.transposeA && !self.transposeB { + self.gpu_kernel_submatrix() + } else { + self.gpu_kernel_single() + } + } + + /// Do calculation of inputA and transpoedB. + /// `transposedInputB` is supposed already transposed + /// + /// - Parameters: + /// - inputABuffer: inputA + /// - inputBBufferTransposed: transposedInputB + /// - outputCBuffer: outputCBuffer + /// - dimInfo: dimInfo + /// - kernel: kernel + internal func gpu_single(inputABuffer: MTLBufferResource, inputBBufferTransposed: MTLBufferResource, outputCBuffer: MTLBufferResource, + dimInfo: inout MatrixDimInfo, kernel: MTLComputePipelineState) { + let commandBuffer = SerranoEngine.configuredEngine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + + // Encoders. + let encoder = commandBuffer!.makeComputeCommandEncoder() + encoder.setComputePipelineState(kernel) + encoder.setBuffer(inputABuffer.buffer, offset: inputABuffer.offset, at: 0) + encoder.setBuffer(inputBBufferTransposed.buffer, offset: inputBBufferTransposed.offset, at: 1) + encoder.setBuffer(outputCBuffer.buffer, offset: outputCBuffer.offset, at: 2) + encoder.setBytes(&dimInfo, length: MemoryLayout.stride, at: 3) + + /// Calculate grid + let threadsPerThreadgroup = MTLSizeMake(kernel.threadExecutionWidth, + kernel.maxTotalThreadsPerThreadgroup / kernel.threadExecutionWidth, + 1) + let threadgroupsPerGrid = MTLSizeMake((Int(dimInfo.M) + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, + (Int(dimInfo.N) + threadsPerThreadgroup.height - 1) / threadsPerThreadgroup.height, + 1) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + encoder.endEncoding() + + if !SerranoLogging.release { + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + } + + + // commit command buffer + commandBuffer!.commit() + commandBuffer!.waitUntilCompleted() + } + + internal func gpu_kernel_single() { + let (kernel, info) = SerranoEngine.configuredEngine.loadGPUKernel(kernelLabel: "MatrixMult_Single") + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel MatrixMult_Single. Info: \(info)") + } + + // transpose B process + var inputBTransposed = self.inputTensors!.last! + if !self.transposeB { + let transposeB = SerranoResourceManager.globalManager.allocateUnamangedTensor(inputBTransposed.shape.transposed()) + let transOp = TransposeOperator(inputTensors: [inputBTransposed], outputTensors: [transposeB]) + transOp.disableInputOutputCheck = true + transOp.compute(.GPU) + inputBTransposed = transposeB + } + let inputBBufferTransposed = SerranoResourceManager.globalManager.allocateMTLBufferResource(inputBTransposed) + + // do calcualtion + let workGroup = DispatchGroup() + if self.transposeA { + for (inputA, output) in zip(self.inputTensors![0...stride)) + self.gpu_single(inputABuffer: buffers[0], inputBBufferTransposed: inputBBufferTransposed, + outputCBuffer: buffers[1], dimInfo: &info, kernel: kernel!) + workGroup.leave() + } + } + } else { + for (inputA, output) in zip(self.inputTensors![0...stride)) + self.gpu_single(inputABuffer: buffers[0], inputBBufferTransposed: inputBBufferTransposed, + outputCBuffer: buffers[1], dimInfo: &info, kernel: kernel!) + workGroup.leave() + } + } + } + + workGroup.wait() + } + + + /// Do matrix multiplication with submatrix kernel. + /// + /// - Parameters: + /// - inputATransposeBuffer: transposed A buffer + /// - inputBBuffer: inputBBuffer + /// - outputCBuffer: outputCBuffer + /// - dimInfo: dimInfo + /// - kernel: kernel + internal func gpu_submatrix(inputATransposeBuffer: MTLBufferResource, inputBBuffer: MTLBufferResource, + outputCBuffer: MTLBufferResource, + dimInfo: inout MatrixDimInfo, kernel: MTLComputePipelineState) { + let commandBuffer = SerranoEngine.configuredEngine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + + // Encoders. + let encoder = commandBuffer!.makeComputeCommandEncoder() + encoder.setComputePipelineState(kernel) + encoder.setBuffer(inputATransposeBuffer.buffer, offset: inputATransposeBuffer.offset, at: 0) + encoder.setBuffer(inputBBuffer.buffer, offset: inputBBuffer.offset, at: 1) + encoder.setBuffer(outputCBuffer.buffer, offset: outputCBuffer.offset, at: 2) + encoder.setBytes(&dimInfo, length: MemoryLayout.stride, at: 3) + + /// Calculate grid + let threadsPerThreadgroup = MTLSizeMake(MatrixMultOperator.METAL_SUBMATRIX_SIZE, + MatrixMultOperator.METAL_SUBMATRIX_SIZE, + 1) + let threadgroupsPerGrid = MTLSizeMake((Int(dimInfo.M) + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, + (Int(dimInfo.N) + threadsPerThreadgroup.height - 1) / threadsPerThreadgroup.height, + 1) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + encoder.endEncoding() + + if !SerranoLogging.release { + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + } + + // commit command buffer + commandBuffer!.commit() + commandBuffer!.waitUntilCompleted() + } + + /// - Note: There's no any transposing processing in this function cause + /// in function `gpu()` it only dispatches suitable inputs to this function. + internal func gpu_kernel_submatrix() { + let (kernel, info) = SerranoEngine.configuredEngine.loadGPUKernel(kernelLabel: "MatrixMult_submatrix") + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel MatrixMult_Single. Info: \(info)") + } + + let inputBBuffer = SerranoResourceManager.globalManager.allocateMTLBufferResource(self.inputTensors!.last!) + + // do calcualtion + let workGroup = DispatchGroup() + + for (inputA, output) in zip(self.inputTensors![0...stride)) + self.gpu_submatrix(inputATransposeBuffer: buffers[0], inputBBuffer: inputBBuffer, outputCBuffer: buffers[1], + dimInfo: &info, kernel: kernel!) + workGroup.leave() + } + } + workGroup.wait() + } + + internal func gpu_kernel_MPS() { + #if !((arch(i386) || arch(x86_64)) && os(iOS)) && !(arch(x86_64))// prevent build error on simulator + let tensors = [self.inputTensors![0], self.inputTensors![1], self.outputTensors![0]] + + //TODO: IMPLEMENT TRANSPOSE ADAPTIVE CODE + // Here allcoate unmanaged, cause we cannot use buffer offset in MPS + let buffers = SerranoResourceManager.globalManager.allocateUnmanagedMTLBuffers(tensors) + + // get matrix + var matrix = [MPSMatrix]() + for (tensor, buffer) in zip(tensors, buffers) { + let descript = MPSMatrixDescriptor(dimensions: tensor.shape.shapeArray[0], columns: tensor.shape.shapeArray[1], + rowBytes: tensor.shape.shapeArray[1] * MemoryLayout.stride, + dataType: .float32) + matrix.append(MPSMatrix(buffer: buffer, descriptor: descript)) + } + + //kernel + let kernel = MPSMatrixMultiplication(device: SerranoEngine.configuredEngine.GPUDevice!, + transposeLeft: self.transposeA, transposeRight: self.transposeB, + resultRows: tensors[2].shape.shapeArray[0], resultColumns: tensors[2].shape.shapeArray[1], + interiorColumns: tensors[0].shape.shapeArray[1], alpha: 1, beta: 0) + + let commandBuffer = SerranoEngine.configuredEngine.serranoCommandQueue!.makeCommandBuffer() + kernel.encode(commandBuffer: commandBuffer, leftMatrix: matrix[0], rightMatrix: matrix[1], resultMatrix: matrix[2]) + commandBuffer.commit() + let startTime = CFAbsoluteTimeGetCurrent() + commandBuffer.waitUntilCompleted() + let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime + + SerranoLogging.stdLogging(message: "Executed GPU calcualtion in \(timeElapsed) seconds.", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + #endif + } + +} diff --git a/Source/Serrano/operators/basic/reduce/reduce_op.metal b/Source/Serrano/operators/basic/reduce/reduce_op.metal new file mode 100644 index 0000000..a539904 --- /dev/null +++ b/Source/Serrano/operators/basic/reduce/reduce_op.metal @@ -0,0 +1,15 @@ +// +// reduce_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + + +namespace serrano_ios { + +} diff --git a/Source/Serrano/operators/basic/reduce/reduce_op.swift b/Source/Serrano/operators/basic/reduce/reduce_op.swift new file mode 100644 index 0000000..b29c629 --- /dev/null +++ b/Source/Serrano/operators/basic/reduce/reduce_op.swift @@ -0,0 +1,641 @@ +// +// reduce_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Accelerate +import Dispatch + + +/** +The abstract class for all reduce operators. Should not be used directly. +A reduce operator do some aggregate calculation along given axes. +An example given by TensorFlow is [here](https://www.tensorflow.org/api_docs/python/tf/reduce_sum). +*/ +public class ReduceOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + public var computationDelegate: OperatorCalculationDelegate? + + public var metalKernelFuncLabel: String + + public var operatorLabel: String + + public var inputTensors: [Tensor]? + + public var outputTensors: [Tensor]? + + /// The axes to do the computation + public var axis: [Int] { + didSet { + self.axis = Array(Set(self.axis)) // remove duplicates + } + } + + /// Indicate if keep dimensions in result tensor. + /// This just affects result tensor's `shape` attributes. + /// Default `false` + public var keepDim: Bool = false + + /// The element compuation block in CPU mode. + /// In most cases, subclass should just override this part in `init` method instead overriding the whole `cpu()` method. + /// The firat pointer is the input tensor, + //// the second is the output tensor + public lazy var cpuComputeBlock: (Tensor, Tensor, [Int]) -> Void = { (inputTensor: Tensor, outputTensor: Tensor, axis: [Int]) -> Void in + print("NEED OVERLOAD") + } + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + /// + /// - Note: All `UnaryOperators` are not trainable. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + init(computationDelegate: OperatorCalculationDelegate?, + operatorLabel: String = "NEED OVERRIDE", + metalKernelFuncLabel kernelLabel: String = "NEED OVERRIDE", + inputTensors: [Tensor]?, outputTensors: [Tensor]?, + axis: [Int]) { + self.computationDelegate = computationDelegate + self.operatorLabel = operatorLabel + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.metalKernelFuncLabel = kernelLabel + self.axis = Array(Set(axis)) + self.cpuComputeBlock = { (inputTensor: Tensor, outputTensor: Tensor, axis: [Int]) -> Void in + print("NEED OVERLOAD") + } + } + + + /// Conenience init. + /// + /// - Parameters: + /// - inputTensors: inputTensors description + /// - outputTensors: outputTensors description + /// - axis: axis description + public convenience required init(inputTensors: [Tensor]? = nil, outputTensors: [Tensor]? = nil, axis: [Int]) { + self.init(computationDelegate: nil, inputTensors: inputTensors, outputTensors: outputTensors, axis: axis) + } + + + /// Conenience init. + /// + /// - Parameters: + /// - axis: axis + /// - keepDim: keepDim + public convenience init(axis: [Int], keepDim: Bool) { + self.init(computationDelegate: nil, inputTensors: nil, outputTensors: nil, axis: axis) + self.keepDim = keepDim + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + + /// Check if for each input shape operator could do reduce operation with `axis` attribute value. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + var outShapes = [TensorShape]() + + // empty input warning + if shapes.count == 0 { + SerranoLogging.warningLogging(message: "The input shapes contains no element.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return outShapes + } + + for shape in shapes { + // check rank + guard shape.rank >= self.axis.count else { + SerranoLogging.errorLogging(message: "Input shape [\(shape)] is not valid on target axis: \(self.axis)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + + var outputShapeArray = shape.shapeArray + // ax dim value check + for ax in self.axis { + guard ax < outputShapeArray.count && ax >= 0 else { + SerranoLogging.errorLogging(message: "Input shape [\(shape)] is not valid on target axis: \(self.axis)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + outputShapeArray[ax] = 1 + } + + // not keeping dim, del dim + if !self.keepDim { + outputShapeArray = outputShapeArray + .enumerated() + .filter{ !self.axis.contains($0.offset) } + .map { $0.element } + } + outShapes.append(TensorShape(dataType: shape.dataType, shape: outputShapeArray)) + } + + return outShapes + } + + + /// Validate shapes`inputTensors` and `outputTensors`. + /// + /// - Returns: check passing and message if not passing + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input not nil + guard self.inputTensors != nil else { + return (false, "Input tensors are nil.") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Output tensors are nil.") + } + + // count + guard self.inputTensors!.count == self.outputTensors!.count else { + return (false, "Input and output tensors should have same amount of tensors. " + + "Input: \(self.inputTensors!.count), output: \(self.outputTensors!.count).") + } + + // check input shapes + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapesValid = self.outputShape(shapeArray: inputShapes) + guard outputShapesValid != nil else { + return (false, "Input tensors' shapes are not valid. Check log for detail.") + } + + // check output shapes + let outputShapes = self.outputTensors!.map { $0.shape } + for i in 0.. [String: DataSymbolSupportedDataType] { + //TODO: Implementation + fatalError("Not implemented") + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + _ = self.gradCompute(computationMode) + } + } + + /// Update params if possible. + /// No update parameters for binary operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + internal func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0.. Void in + let op = ReduceOperator(axis: [Int]()) + op.keepDim = true + + var inputAddress = inputTensor.contentsAddress + var outptuAddress: UnsafeMutablePointer + var inputShape = inputTensor.shape + var intermediateTensor: Tensor = inputTensor + var nextTensor: Tensor = outputTensor + + let sortedAxis = axis.sorted() + for (axIndex, ax) in sortedAxis.enumerated() { + // decide output address + if axIndex != sortedAxis.count - 1 { + // intermediate + op.axis = [ax] + let nextShape = op.outputShape(shapeArray: [inputShape])!.first! + nextTensor = Tensor(repeatingValue: 0.0, tensorShape: nextShape) + outptuAddress = nextTensor.contentsAddress + } else { + nextTensor = outputTensor + } + outptuAddress = nextTensor.contentsAddress + + // do reduce on intermediateTensor and store result to newTensor + let preDimCount = intermediateTensor.shape.shapeArray.prefix(upTo: ax).reduce(1, *) + let nextEntryCount = intermediateTensor.shape.shapeArray.suffix(from: ax+1).reduce(1, *) + let entryCount = intermediateTensor.shape.shapeArray.suffix(from: ax).reduce(1, *) + let axDim = intermediateTensor.shape.shapeArray[ax] + for preDimIndex in 0.. Void in + let op = ReduceOperator(axis: [Int]()) + op.keepDim = true + + var inputAddress = inputTensor.contentsAddress + var outptuAddress: UnsafeMutablePointer + var inputShape = inputTensor.shape + var intermediateTensor: Tensor = inputTensor + var nextTensor: Tensor = outputTensor + + let sortedAxis = axis.sorted() + for (axIndex, ax) in sortedAxis.enumerated() { + // decide output address + if axIndex != sortedAxis.count - 1 { + // intermediate + op.axis = [ax] + let nextShape = op.outputShape(shapeArray: [inputShape])!.first! + nextTensor = Tensor(repeatingValue: 0.0, tensorShape: nextShape) + outptuAddress = nextTensor.contentsAddress + } else { + nextTensor = outputTensor + } + outptuAddress = nextTensor.contentsAddress + + // do reduce on intermediateTensor and store result to newTensor + let preDimCount = intermediateTensor.shape.shapeArray.prefix(upTo: ax).reduce(1, *) + let nextEntryCount = intermediateTensor.shape.shapeArray.suffix(from: ax+1).reduce(1, *) + let entryCount = intermediateTensor.shape.shapeArray.suffix(from: ax).reduce(1, *) + let axDim = intermediateTensor.shape.shapeArray[ax] + for preDimIndex in 0.. Void in + let op = ReduceOperator(axis: [Int]()) + op.keepDim = true + + var inputAddress = inputTensor.contentsAddress + var outptuAddress: UnsafeMutablePointer + var inputShape = inputTensor.shape + var intermediateTensor: Tensor = inputTensor + var nextTensor: Tensor = outputTensor + + let sortedAxis = axis.sorted() + for (axIndex, ax) in sortedAxis.enumerated() { + // decide output address + if axIndex != sortedAxis.count - 1 { + // intermediate + op.axis = [ax] + let nextShape = op.outputShape(shapeArray: [inputShape])!.first! + nextTensor = Tensor(repeatingValue: 0.0, tensorShape: nextShape) + outptuAddress = nextTensor.contentsAddress + } else { + nextTensor = outputTensor + } + outptuAddress = nextTensor.contentsAddress + + // do reduce on intermediateTensor and store result to newTensor + let preDimCount = intermediateTensor.shape.shapeArray.prefix(upTo: ax).reduce(1, *) + let nextEntryCount = intermediateTensor.shape.shapeArray.suffix(from: ax+1).reduce(1, *) + let entryCount = intermediateTensor.shape.shapeArray.suffix(from: ax).reduce(1, *) + let axDim = intermediateTensor.shape.shapeArray[ax] + for preDimIndex in 0.. Void in + let op = ReduceOperator(axis: [Int]()) + op.keepDim = true + + var inputAddress = inputTensor.contentsAddress + var outptuAddress: UnsafeMutablePointer + var inputShape = inputTensor.shape + var intermediateTensor: Tensor = inputTensor + var nextTensor: Tensor = outputTensor + + let sortedAxis = axis.sorted() + for (axIndex, ax) in sortedAxis.enumerated() { + // decide output address + if axIndex != sortedAxis.count - 1 { + // intermediate + op.axis = [ax] + let nextShape = op.outputShape(shapeArray: [inputShape])!.first! + nextTensor = Tensor(repeatingValue: 0.0, tensorShape: nextShape) + outptuAddress = nextTensor.contentsAddress + } else { + nextTensor = outputTensor + } + outptuAddress = nextTensor.contentsAddress + + // do reduce on intermediateTensor and store result to newTensor + let preDimCount = intermediateTensor.shape.shapeArray.prefix(upTo: ax).reduce(1, *) + let nextEntryCount = intermediateTensor.shape.shapeArray.suffix(from: ax+1).reduce(1, *) + let entryCount = intermediateTensor.shape.shapeArray.suffix(from: ax).reduce(1, *) + let axDim = intermediateTensor.shape.shapeArray[ax] + for preDimIndex in 0.. Void in + let op = ReduceOperator(axis: [Int]()) + op.keepDim = true + + var inputAddress = inputTensor.contentsAddress + var outptuAddress: UnsafeMutablePointer + var inputShape = inputTensor.shape + var intermediateTensor: Tensor = inputTensor + var nextTensor: Tensor = outputTensor + var placeHolder: Float = 1.0 + + let sortedAxis = axis.sorted() + for (axIndex, ax) in sortedAxis.enumerated() { + // decide output address + if axIndex != sortedAxis.count - 1 { + // intermediate + op.axis = [ax] + let nextShape = op.outputShape(shapeArray: [inputShape])!.first! + nextTensor = Tensor(repeatingValue: 0.0, tensorShape: nextShape) + outptuAddress = nextTensor.contentsAddress + } else { + nextTensor = outputTensor + } + outptuAddress = nextTensor.contentsAddress + + // do reduce on intermediateTensor and store result to newTensor + let preDimCount = intermediateTensor.shape.shapeArray.prefix(upTo: ax).reduce(1, *) + let nextEntryCount = intermediateTensor.shape.shapeArray.suffix(from: ax+1).reduce(1, *) + let entryCount = intermediateTensor.shape.shapeArray.suffix(from: ax).reduce(1, *) + let axDim = intermediateTensor.shape.shapeArray[ax] + for preDimIndex in 0.. [TensorShape]? { + return shapes + } + + /// The `inputTensors` should not be `nil`. + /// + /// + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // inputTensors should not be `nil` + guard self.inputTensors != nil else { + return (false, "Operator \(self.operatorLabel) should have valid inputTensors.") + } + + guard self.outputTensors != nil else { + return (false, "Operator \(self.operatorLabel) should have valid outputTensors.") + } + + // if assigned outputTensors, check match + guard self.inputTensors!.count == self.outputTensors!.count else { + return (false, "Operator \(self.operatorLabel) should have same amount of input tensors and output tensors. " + + "Given \(self.inputTensors!.count) inputTensors and \(self.outputTensors!.count) outputTensors") + } + + for i in 0...stride) + } + } + + + /// Compute Async + /// + /// - Parameters: + /// - tensors: tensors description + /// - computationMode: computationMode description + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + OperatorUtils.delegateNilWarning(op: self, file: #file, function: #function, line: #line) + self.computationDelegate?.operatorWillBeginComputation(self) + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + } + + /// Calulate grads sync. + /// Copy operator itself does not generate any grads. Should be ignored in gaph AD. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return `upGrads` if not nil. Else return an empty array. + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + return [:] + } + + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + /// - upGrds: upGrds + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + + + /// No updatable parameters. + /// This function just returns. + /// + /// - Parameters: + /// - grads: grads + /// - LR: LR + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } +} diff --git a/Source/Serrano/operators/basic/unary/unary_op.metal b/Source/Serrano/operators/basic/unary/unary_op.metal new file mode 100644 index 0000000..c0a782b --- /dev/null +++ b/Source/Serrano/operators/basic/unary/unary_op.metal @@ -0,0 +1,264 @@ +// +// unary_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +namespace serrano_ios { + kernel void Sin(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = sin(in_tensor[gid.x]); + } + + kernel void Arcsin(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = asin(in_tensor[gid.x]); + } + + kernel void Cos(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = cos(in_tensor[gid.x]); + } + + kernel void Tan(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = tan(in_tensor[gid.x]); + } + + kernel void Arctan(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = atan(in_tensor[gid.x]); + } + + kernel void Abs(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = abs(in_tensor[gid.x]); + } + + kernel void Degree(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = 180 / M_PI_F * in_tensor[gid.x] ; + } + + kernel void Arccos(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = acos(in_tensor[gid.x]); + } + + kernel void Radien(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = M_PI_F / 180 * in_tensor[gid.x]; + } + + kernel void Sinh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = sinh(in_tensor[gid.x]); + } + + kernel void Cosh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = cosh(in_tensor[gid.x]); + } + + kernel void Tanh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = tanh(in_tensor[gid.x]); + } + + kernel void Arctanh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = atanh(in_tensor[gid.x]); + } + + kernel void Arccosh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = acosh(in_tensor[gid.x]); + } + + kernel void Arcsinh(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = asinh(in_tensor[gid.x]); + } + + kernel void Floor(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = floor(in_tensor[gid.x]); + } + + kernel void Ceil(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = ceil(in_tensor[gid.x]); + } + + kernel void Rint(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = rint(in_tensor[gid.x]); + } + + kernel void Round(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = round(in_tensor[gid.x]); + } + + kernel void Square(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = in_tensor[gid.x] * in_tensor[gid.x]; + } + + kernel void Rsqrt(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = rsqrt(in_tensor[gid.x]); + } + + kernel void Sqrt(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = sqrt(in_tensor[gid.x]); + } + + kernel void Log1p(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = log(in_tensor[gid.x] + 1); + } + + kernel void Log2(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = log2(in_tensor[gid.x]); + } + + kernel void Log10(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = log10(in_tensor[gid.x]); + } + + kernel void Log(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = log(in_tensor[gid.x]); + } + + kernel void Expm1(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = exp(in_tensor[gid.x]) - 1; + } + + kernel void Exp(device float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint* count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) + { + if (gid.x >= count[0]) return; + out_tensor[gid.x] = exp(in_tensor[gid.x]); + } +} diff --git a/Source/Serrano/operators/basic/unary/unary_op.swift b/Source/Serrano/operators/basic/unary/unary_op.swift new file mode 100644 index 0000000..40cf048 --- /dev/null +++ b/Source/Serrano/operators/basic/unary/unary_op.swift @@ -0,0 +1,1506 @@ +// +// unary_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/2/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Dispatch +import Metal +import Accelerate + +/** + Abstract class define the standard unary operator working flow. + This class should not be used directly. + Any class inheritance this class is doing element-wise computation for input tensors. + */ +public class UnaryOperator: ComputableOperator { + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String = "" + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// The element compuation block in CPU mode. + /// In most cases, subclass should just override this part in `init` method instead overriding the whole `cpu()` method. + /// The firat pointer is the input tensor, + //// the second is the output tensor + public var cpuElementComputationBlock: (Tensor, Tensor) -> Void + + + /// The grad compuation block. + /// parameter: inputTensors, + public var gradComputationBlock: ([Tensor], OperatorComputationMode) -> [DataSymbolSupportedDataType] + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + /// + /// - Note: All `UnaryOperators` are not trainable. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /// Designated init function + /// + /// - Parameters: + /// - label: label description + /// - delegate: delegate description + init(operatorLabel label: String, + cpuComputeBlock block: @escaping (Tensor, Tensor) -> Void , + gradComputationBlock gradBlock: @escaping ([Tensor], OperatorComputationMode) -> [DataSymbolSupportedDataType], + metalKernelFuncLabel kernelLabel: String, + computationDelegate: OperatorCalculationDelegate?, + inputTensors: [Tensor]?, + outputTensors: [Tensor]?) { + self.operatorLabel = label + self.computationDelegate = computationDelegate + self.metalKernelFuncLabel = kernelLabel + self.cpuElementComputationBlock = block + self.gradComputationBlock = gradBlock + self.inputTensors = inputTensors + self.outputTensors = outputTensors + } + + /// Convenience initializer + /// Subclass should override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + public convenience required init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, oututTensor: Tensor) -> Void in + fatalError() + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + fatalError() + } + let defaultLabel = "NEED OVERRIDE" + let kernelLabel = "NEED OVERRIDE" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + + /// Initial by assign input and output tensors + /// + /// - Parameters: + /// - inputTensors: inputTensors description + /// - outputTensors: outputTensors description + public convenience init(inputTensors:[Tensor], outputTensors:[Tensor]) { + self.init(computationDelegate: nil) + self.inputTensors = inputTensors + self.outputTensors = outputTensors + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /// This operator would not do anything about shapes. + /// Basically, it just return input shapes identically. + /// + /// - Note: If the `shapeArray` is empty, function returns `nil`. + /// + /// - Parameter shapes: input shapes + /// - Returns: return shapes + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + guard shapes.count != 0 else { + SerranoLogging.warningLogging(message: "Input shapes are empty", file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + return shapes + } + + /// The `inputTensors` and `outputTensors` should be have same count of tensors and each tensor should be has same dimension. + /// + /// + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input not nil + guard self.inputTensors != nil else { + return (false, "Input tensors are nil.") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Output tensors are nil.") + } + + // same count + guard self.outputTensors!.count == self.inputTensors!.count else { + return (false, "Input tensors count is \(self.inputTensors!.count). " + + "Output tensors count is \(self.outputTensors!.count)." + + "Should be equal.") + } + + // input shape check + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapeCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapeCheck != nil else { + return (false, "Input tensors shapes are invalid. Check log for details.") + } + + // output shape check + let outputShapes = self.outputTensors!.map { $0.shape } + for (i, t) in zip(outputShapes, outputShapeCheck!).enumerated() { + guard t.0 == t.1 else { + return (false, "Expect output tensor shape \(t.1.description), given \(t.0.description) at index \(i)") + } + } + + return (true, "") + } + + /// Compute asynclly + /// + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: computation mode + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Compute synclly. + /// + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: cmputation mode. If choose `GPU` but haven't configued a GPU SerranoEngine, operator will use `CPU` to compute. + /// - Returns: result tensors + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + if !self.disableInputOutputCheck { + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) calculation aborted cause invalid input tensors or output tensors: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + let grads = self.gradComputationBlock(self.inputTensors!, computationMode) + var result = [String: DataSymbolSupportedDataType]() + for (i, grad) in grads.enumerated() { + result["input_\(i)"] = grad + } + return result + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + +// /// An unary operator's representation graph just has inputs tensor symbols, output tensor symbols +// /// and an operator symbol. +// /// +// /// - Returns: graph object. +// public func addedToGraph(with InputSymbols: [TensorSymbol]) -> Graph { +// let graph = ComputationGraph() +// +// let outputSymbols = +// +// return graph +// } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + /// Use cpu do the inplace computation. + /// Default, `UnaryOperator` defines a workflow. Subclass just needs to override `cpuElementComputationBlock`. + /// If subclass needs custom flow, it could just override this function. + /// + /// - Parameter tensors: the operation tensors + internal func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0...size) + guard countBuffer != nil else { + fatalError("[Serrano] Failed to careate MTLBuffer.") + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + // encoder + let encoder = commandBuffer!.makeComputeCommandEncoder() + encoder.setComputePipelineState(kernel!) + encoder.setBuffer(inputBuffers[bufferIndex].buffer, offset: inputBuffers[bufferIndex].offset, at: 0) + encoder.setBuffer(resultBuffers[bufferIndex].buffer, offset: resultBuffers[bufferIndex].offset, at: 1) + encoder.setBuffer(countBuffer, offset: 0, at: 2) + + // dispatch + let threadsPerThreadgroup = MTLSizeMake(kernel!.threadExecutionWidth, + 1, + 1) + let threadgroupsPerGrid = MTLSizeMake((self.inputTensors![bufferIndex].count + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, + 1, + 1) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + encoder.endEncoding() + } + + // commit command buffer + commandBuffer!.commit() + commandBuffer!.waitUntilCompleted() + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +/** +Compute element-wise sine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class SinOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvsinf(outputAddress, inputAddress, &count) + } + + // cos(x) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let cosOp = CosOperator(inputTensors: inputs, outputTensors: grads) + cosOp.disableInputOutputCheck = true + cosOp.compute(mode) + return grads + } + + let defaultLabel = "SinOperator" + let kernelLabel = "Sin" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise tangent on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class TanOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvtanf(outputAddress, inputAddress, &count) + } + + // 1 / cos(x)^2 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let cosOp = CosOperator(inputTensors: inputs, outputTensors: grads) + cosOp.disableInputOutputCheck = true + cosOp.compute(mode) + for grad in grads { + grad &* grad + 1.0 &/ grad + } + return grads + } + + let defaultLabel = "TanOperator" + let kernelLabel = "Tan" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise cosine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class CosOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvcosf(outputAddress, inputAddress, &count) + } + + // sin(x) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let sinOp = SinOperator(inputTensors: inputs, outputTensors: grads) + sinOp.disableInputOutputCheck = true + sinOp.compute(mode) + return grads + } + + let defaultLabel = "CosOperator" + let kernelLabel = "Cos" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise arc sine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArcsinOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvasinf(outputAddress, inputAddress, &count) + } + + // 1 / sqrt(1-x^2) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let copyOp = CopyOperator(inputTensors: inputs, outputTensors: grads) + copyOp.disableInputOutputCheck = true + copyOp.compute(mode) + for grad in grads { + 1.0 &- (grad &* grad) + let sqrtOp = SqrtOperator(inputTensors: [grad], outputTensors: [grad]) + sqrtOp.disableInputOutputCheck = true + sqrtOp.compute(mode) + 1 &/ grad + } + return grads + } + let defaultLabel = "ArcsinOperator" + let kernelLabel = "Arcsin" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise arc cosine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArccosOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvacosf(outputAddress, inputAddress, &count) + } + + // -1/sqrt(1-x^2) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let copyOp = CopyOperator(inputTensors: inputs, outputTensors: grads) + copyOp.disableInputOutputCheck = true + copyOp.compute(mode) + for grad in grads { + 1.0 &- (grad &* grad) + let sqrtOp = SqrtOperator(inputTensors: [grad], outputTensors: [grad]) + sqrtOp.disableInputOutputCheck = true + sqrtOp.compute(mode) + -1.0 &/ grad + } + return grads + } + + let defaultLabel = "ArccosOperator" + let kernelLabel = "Arccos" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise arc tangent on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArctanOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvatanf(outputAddress, inputAddress, &count) + } + + // 1 / (1 + x^2) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + // First allocate as managed to speed up incase using GPU with reusing MTLBuffers + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // copy + let copyOp = CopyOperator(inputTensors: inputs, outputTensors: grads) + copyOp.disableInputOutputCheck = true + copyOp.compute(mode) + + for grad in grads { + 1.0 &/ (1.0 &+ (grad &* grad)) + } + + + return grads + } + + let defaultLabel = "ArctanOperator" + let kernelLabel = "Arctan" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise of input tensors radians to degrees. + */ +public class DegreeOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + let count = vDSP_Length(output.count) + var convert:Float = 180.0 / 3.1415926 + vDSP_vsmul(inputAddress, 1, &convert, outputAddress, 1, count) + } + + // 180.0 / 3.1415926 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + let val:Float = 180.0 / 3.1415926 + for input in inputs { + grads.append(Tensor(repeatingValue: val, tensorShape: input.shape)) + } + + + return grads + } + + let defaultLabel = "DegreeOperator" + let kernelLabel = "Degree" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise abs values of input tensors. + */ +public class AbsOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + let count = UInt(output.count) + vDSP_vabs(inputAddress, 1, outputAddress, 1, count) + } + + // x / |x|. Note x != 0. or calcualted value is NaN. + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // abs + let absOp = AbsOperator(inputTensors: inputs, outputTensors: grads) + absOp.disableInputOutputCheck = true + absOp.compute(mode) + + // div + for (input, grad) in zip(inputs, grads) { + let rdivOp = DivOperator(inputTensors: [input, grad], outputTensors: [grad]) + rdivOp.disableInputOutputCheck = true + rdivOp.compute(mode) + } + + + return grads + } + + let defaultLabel = "AbsOperator" + let kernelLabel = "Abs" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise radien values of input tensors fro degrees. + */ +public class RadianOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + let count = vDSP_Length(output.count) + var convert:Float = 3.1415926 / 180.0 + vDSP_vsmul(inputAddress, 1, &convert, outputAddress, 1, count) + } + + // 3.1415926 / 180.0 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + let val:Float = 3.1415926 / 180.0 + for input in inputs { + grads.append(Tensor(repeatingValue: val, tensorShape: input.shape)) + } + + + return grads + } + + let defaultLabel = "RadienOperator" + let kernelLabel = "Radien" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise hyperbolic sine of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class SinhOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvsinhf(outputAddress, inputAddress, &count) + } + + // (e^x + e^-x) / 2 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // e^x + let expOp = ExpOperator(inputTensors: inputs, outputTensors: grads) + expOp.disableInputOutputCheck = true + expOp.compute(mode) + + // e^-x + var eNegative = [Tensor]() + for input in inputs { eNegative.append(-1 * input) } + expOp.inputTensors = eNegative + expOp.outputTensors = eNegative + expOp.compute(mode) + + for (grad, negtivate) in zip(grads, eNegative) { + (grad &+ negtivate) &/ 2 + } + + + return grads + } + + let defaultLabel = "SinhOperator" + let kernelLabel = "Sinh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise hyperbolic cosine of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class CoshOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvcoshf(outputAddress, inputAddress, &count) + } + + // (e^x - e^-x) / 2 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // e^x + let expOp = ExpOperator(inputTensors: inputs, outputTensors: grads) + expOp.disableInputOutputCheck = true + expOp.compute(mode) + + // e^-x + var eNegative = [Tensor]() + for input in inputs { eNegative.append(-1 * input) } + expOp.inputTensors = eNegative + expOp.outputTensors = eNegative + expOp.compute(mode) + + for (grad, negtivate) in zip(grads, eNegative) { + (grad &- negtivate) &/ 2 + } + + + return grads + } + + let defaultLabel = "CoshOperator" + let kernelLabel = "Cosh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise hyperbolic tangent of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class TanhOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvtanhf(outputAddress, inputAddress, &count) + } + + // 1 / cosh(x) ^ 2 + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + let coshOp = CoshOperator(inputTensors: inputs, outputTensors: grads) + coshOp.disableInputOutputCheck = true + coshOp.compute(mode) + + for grad in grads { 1.0 &/ (grad &* grad) } + + + return grads + } + + let defaultLabel = "TanhOperator" + let kernelLabel = "Tanh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise inverse hyperbolic tangent on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArctanhOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvatanhf(outputAddress, inputAddress, &count) + } + + // 1 / (1 - x^2) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + let squareOp = SquareOperator(inputTensors: inputs, outputTensors: grads) + squareOp.disableInputOutputCheck = true + squareOp.compute(mode) + + for grad in grads { 1.0 &/ (1.0 &- grad) } + + + + // release from management pool + DispatchQueue.global(qos: .userInitiated).async { SerranoResourceManager.globalManager.releaseTensors(grads) } + return grads + } + + let defaultLabel = "ArctanhOperator" + let kernelLabel = "Arctanh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise inverse hyperbolic cosine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArccoshOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvacoshf(outputAddress, inputAddress, &count) + } + + // 1 / sqrt(x^2 - 1) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // square + let squreOp = SquareOperator(inputTensors: inputs, outputTensors: grads) + squreOp.disableInputOutputCheck = true + squreOp.compute(mode) + + for grad in grads { grad &- 1.0 } + let sqrtOp = SqrtOperator(inputTensors: inputs, outputTensors: grads) + sqrtOp.disableInputOutputCheck = true + sqrtOp.compute(mode) + + for grad in grads { 1.0 &/ grad } + + + return grads + } + + let defaultLabel = "ArccoshOperator" + let kernelLabel = "Arccosh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise inverse hyperbolic cosine on input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ArcsinhOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvasinhf(outputAddress, inputAddress, &count) + } + + // 1 / sqrt(x^2 +1) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + // square + let squreOp = SquareOperator(inputTensors: inputs, outputTensors: grads) + squreOp.disableInputOutputCheck = true + squreOp.compute(mode) + + for grad in grads { grad &+ 1.0 } + let sqrtOp = SqrtOperator(inputTensors: inputs, outputTensors: grads) + sqrtOp.disableInputOutputCheck = true + sqrtOp.compute(mode) + + for grad in grads { 1.0 &/ grad } + + + return grads + } + + let defaultLabel = "ArcsinhOperator" + let kernelLabel = "Arcsinh" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Operator computes element-wise floor of the input tensors and returen result tensors. + */ +public class FloorOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvfloorf(outputAddress, inputAddress, &count) + } + + // 0 for any inputs. + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + // 0 + for input in inputs { grads.append(Tensor(repeatingValue: 0.0, tensorShape: input.shape)) } + + + return grads + } + + let defaultLabel = "FloorOperator" + let kernelLabel = "Floor" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Operator computes element-wise floor of the input tensors and returen result tensors. + */ +public class CeilOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvceilf(outputAddress, inputAddress, &count) + } + + // 0 for any inputs. + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + // 0 + for input in inputs { grads.append(Tensor(repeatingValue: 0.0, tensorShape: input.shape)) } + + + return grads + } + + let defaultLabel = "CeilOperator" + let kernelLabel = "Ceil" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Operator computes element-wise rounded value to the nearest integer of the input. + */ +public class RintOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvnintf(outputAddress, inputAddress, &count) + } + + // 0 for any inputs. + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + // 0 + for input in inputs { grads.append(Tensor(repeatingValue: 0.0, tensorShape: input.shape)) } + + + return grads + } + + let defaultLabel = "RintOperator" + let kernelLabel = "Rint" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Operator computes element-wise rounded value to the truncating integers of input values. + */ +public class RoundOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvintf(outputAddress, inputAddress, &count) + } + + // 0 for any inputs. + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + // 0 + for input in inputs { grads.append(Tensor(repeatingValue: 0.0, tensorShape: input.shape)) } + + + return grads + } + + let defaultLabel = "RoundOperator" + let kernelLabel = "Round" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise square values of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class SquareOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + let count = UInt(output.count) + vDSP_vsq(inputAddress, 1, outputAddress, 1, count) + } + + // 2x + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + for input in inputs { grads.append( 2.0 * input ) } + + + return grads + } + + let defaultLabel = "SquareOperator" + let kernelLabel = "Square" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** +Compute element-wise reciprocal values of square-root of input tensors. +`1 / sqrt(x)` + */ +public class RsqrtOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvrsqrtf(outputAddress, inputAddress, &count) + } + + // -0.5 * x^(-1.5) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + let powOp = PowOperator() + powOp.disableInputOutputCheck = true + for (grad, input) in zip(grads, inputs) { + let const = Tensor(repeatingValue: -1.5, tensorShape: input.shape) + powOp.inputTensors = [input, const] + powOp.outputTensors = [grad] + powOp.compute(mode) + -0.5 &* grad + } + + + return grads + } + + let defaultLabel = "RsqrtOperator" + let kernelLabel = "Rsqrt" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise square-root values of input tensors. + */ +public class SqrtOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvsqrtf(outputAddress, inputAddress, &count) + } + + // 0.5 * x^(-0.5) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + + let powOp = PowOperator() + powOp.disableInputOutputCheck = true + for (grad, input) in zip(grads, inputs) { + let const = Tensor(repeatingValue: -0.5, tensorShape: input.shape) + powOp.inputTensors = [input, const] + powOp.outputTensors = [grad] + powOp.compute(mode) + 0.5 &* grad + } + + + return grads + } + + let defaultLabel = "SqrtOperator" + let kernelLabel = "Sqrt" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** +Compute element-wise `log(1 + x)` values of input tensors. +Base is `e`. + */ +public class Log1pOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvlog1pf(outputAddress, inputAddress, &count) + } + + // 1 / (ln(10) * (1+x)) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + let ln10: Float = log(10.0) + for input in inputs { + grads.append(1.0 &/ (ln10 &* (1.0 + input))) + } + + + return grads + } + + let defaultLabel = "Log1pOperator" + let kernelLabel = "Log1p" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise `log2(x)` values of input tensors. + */ +public class Log2Operator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvlog2f(outputAddress, inputAddress, &count) + } + + // 1 / (ln(2) * x) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + let ln2: Float = log(2.0) + for input in inputs { + grads.append(1.0 &/ (ln2 * input)) + } + + + return grads + } + + let defaultLabel = "Log2Operator" + let kernelLabel = "Log2" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + + +/** + Compute element-wise `log10(x)` values of input tensors. + */ +public class Log10Operator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvlog10f(outputAddress, inputAddress, &count) + } + + // 1 / (ln(10) * x) + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + let ln10: Float = log(10.0) + for input in inputs { + grads.append(1.0 &/ (ln10 * input)) + } + + + return grads + } + + let defaultLabel = "Log10Operator" + let kernelLabel = "Log10" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** +Compute element-wise `log(x)` values of input tensors. +Base is `e`. + */ +public class LogOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvlogf(outputAddress, inputAddress, &count) + } + + // 1 / x + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + var grads = [Tensor]() + for input in inputs { + grads.append(1.0 / input) + } + + + return grads + } + + let defaultLabel = "LogOperator" + let kernelLabel = "Log" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** + Compute element-wise `exp(x) - 1` values of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class Expm1Operator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvexpm1f(outputAddress, inputAddress, &count) + } + + // e^x + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let expOp = ExpOperator(inputTensors: inputs, outputTensors: grads) + expOp.disableInputOutputCheck = true + expOp.compute(mode) + + return grads + } + + let defaultLabel = "Expm1Operator" + let kernelLabel = "Expm1" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + + +/** + Compute element-wise `exp(x)` values of input tensors. + +- Note: This operator may output `NaN` or `Infinite` values and operator would not check these situations. + */ +public class ExpOperator: UnaryOperator { + + /// Override init + /// + /// - Parameter computationDelegate: delegate + public required convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (input: Tensor, output: Tensor) -> Void in + let inputAddress = input.contentsAddress + let outputAddress = output.contentsAddress + var count = Int32(output.count) + vvexpf(outputAddress, inputAddress, &count) + } + + // e^x + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + let grads = SerranoResourceManager.globalManager.allocateUnamangedTensors(inputs.map{$0.shape}) + let expOp = ExpOperator(inputTensors: inputs, outputTensors: grads) + expOp.disableInputOutputCheck = true + expOp.compute(mode) + return grads + } + + let defaultLabel = "ExpOperator" + let kernelLabel = "Exp" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} diff --git a/Source/Serrano/operators/nn/activation/activation_op.metal b/Source/Serrano/operators/nn/activation/activation_op.metal new file mode 100644 index 0000000..97bdc2d --- /dev/null +++ b/Source/Serrano/operators/nn/activation/activation_op.metal @@ -0,0 +1,100 @@ +// +// activation_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 6/26/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include + +using namespace metal; + +namespace serrano_ios { + kernel void ReLU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float& alpha [[buffer(3)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = max(alpha, in_tensor[gid.x]); + } + + kernel void Sigmoid(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = 1.0f / (1.0f + exp(-in_tensor[gid.x])); + } + + kernel void Softplus(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = log(exp(in_tensor[gid.x]) + 1.0f); + } + + kernel void Softsign(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = in_tensor[gid.x] / ( 1.0f + abs(in_tensor[gid.x])); + } + + kernel void Linear(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = in_tensor[gid.x]; + } + + kernel void ELU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float& alpha [[buffer(3)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = (in_tensor[gid.x] >= 0.0f ? in_tensor[gid.x] : alpha*(exp(in_tensor[gid.x]) - 1.0f)); + } + + kernel void SELU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float& alpha [[buffer(3)]], + constant float& scale [[buffer(4)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = (in_tensor[gid.x] >= 0.0f ? scale * in_tensor[gid.x] : scale * alpha * (exp(in_tensor[gid.x]) - 1.0f)); + } + + kernel void LeakyReLU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float& alpha [[buffer(3)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = (in_tensor[gid.x] >= 0.0f ? in_tensor[gid.x] : alpha * in_tensor[gid.x]); + } + + kernel void ThresholdedReLU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float& alpha [[buffer(3)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = (in_tensor[gid.x] > alpha ? in_tensor[gid.x] : 0.0f); + } + + kernel void PReLU(constant float* in_tensor [[ buffer(0) ]], + device float* out_tensor [[ buffer(1) ]], + constant uint& count [[ buffer(2) ]], + constant float* alpha [[buffer(3)]], + uint2 gid [[ thread_position_in_grid ]]) { + if (gid.x >= count) return; + out_tensor[gid.x] = (in_tensor[gid.x] >= 0.0f ? in_tensor[gid.x] : in_tensor[gid.x] * alpha[gid.x]); + } +} diff --git a/Source/Serrano/operators/nn/activation/activation_op.swift b/Source/Serrano/operators/nn/activation/activation_op.swift new file mode 100644 index 0000000..8cecbc1 --- /dev/null +++ b/Source/Serrano/operators/nn/activation/activation_op.swift @@ -0,0 +1,1247 @@ +// +// activation_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/26/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + +/** +The abstract class for all activation operator. +An activation operator is actually a special kind of `UnaryOperator` doing a little bit more complex element-wise computation. +*/ +public class ActivationOperator: UnaryOperator { + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, oututTensor: Tensor) -> Void in + fatalError("Not implemented") + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + fatalError("Not implemented") + } + let defaultLabel = "NEED OVERRIDE" + let kernelLabel = "NEED OVERRIDE" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + /// Construct an `ActivationOperator` from a `String` name. + public convenience init(activationOpName: String) { + fatalError() + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** +Rectified Linear Unit, +`y=max(x,alpha=0.0)` +*/ +public class ReLUOperator: ActivationOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// hyperparameter, default is `0.0` + public var alpha: Float = 0.0 + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, oututTensor: Tensor) -> Void in + fatalError("Not implemented") + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "ReLUOperator" + let kernelLabel = "ReLU" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + /// Initial by setting `alpha` value + /// + /// - Parameters: + /// - computationDelegate: computationDelegate description + /// - alpha: alpha description + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, alpha: Float) { + self.init(computationDelegate: computationDelegate) + self.alpha = alpha + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Attribute `alpha` as a `ScalarSymbol`. + /// + /// - Returns: Array of GraphSymbol + public override func paramSymbols() -> [GraphSymbol] { + let alpha = SerranoScalarSymbol("alpha", dataType: .float, dataSource: .Default) + alpha.bindedData = Float(0.0) + return [alpha] + } + + /// Override CPU + internal override func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0...size) + guard countBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + countBuffers.append(countBuffer!) + + } + resourcePrepareGroup.leave() + } + + // alpha buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + alphaBuffer = engine.GPUDevice?.makeBuffer(bytes: &self.alpha, + length: MemoryLayout.size) + guard alphaBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(alphaBuffer!.length) bytes] requested for alpha value \(self.alpha) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + resourcePrepareGroup.leave() + } + resourcePrepareGroup.wait() + + for bufferIndex in 0.. Void in + let inputPointer = inputTensor.contentsAddress + let outputPointer = outputTensor.contentsAddress + let oneValueTensor = Tensor(repeatingValue: 1.0, tensorShape: outputTensor.shape) + let oneValuePointer = oneValueTensor.contentsAddress + var count32 = Int32(outputTensor.count) + let count = vDSP_Length(outputTensor.count) + //copy value from input to output + cblas_scopy(count32, inputPointer, 1, outputPointer, 1) + // negative + vDSP_vneg(outputPointer, 1, outputPointer, 1, count) + // exp + vvexpf(outputPointer, outputPointer, &count32) + // plus + cblas_saxpy(count32, 1.0, oneValuePointer, 1, outputPointer, 1) + // reciprocal + vvrecf(outputPointer, outputPointer, &count32) + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "SigmoidOperator" + let kernelLabel = "Sigmoid" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** +Softplus.`y = log(exp(x) + 1)` +*/ +public class SoftplusOperator: ActivationOperator { + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + let inAddress = inputTensor.contentsAddress + let outAddress = outputTensor.contentsAddress + let count = vDSP_Length(inputTensor.count) + var count32 = Int32(inputTensor.count) + var one: Float = 1.0 + // exp + vvexpf(outAddress, inAddress, &count32) + // plus 1 + vDSP_vsadd(outAddress, 1, &one, outAddress, 1, count) + // log + vvlogf(outAddress, outAddress, &count32) + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "SoftplusOperator" + let kernelLabel = "Softplus" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + + +/** +Softsign.`y = x / (1 + abs(x))` +*/ +public class SoftsignOperator: ActivationOperator { + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + let inputAddress = inputTensor.contentsAddress + let outAddress = outputTensor.contentsAddress + let count = vDSP_Length(outputTensor.count) + var one:Float = 1.0 + // abs + vDSP_vabs(inputAddress, 1, outAddress, 1, count) + // add + vDSP_vsadd(outAddress, 1, &one, outAddress, 1, count) + // div + vDSP_vdiv(outAddress, 1, inputAddress, 1, outAddress, 1, count) + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "SoftsignOperator" + let kernelLabel = "Softsign" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + + +/** +Do nothing. Output identify tensor. +`y = x` +*/ +public class LinearOperator: ActivationOperator { + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + let inputPointer = inputTensor.contentsAddress + let outputPointer = outputTensor.contentsAddress + let count32 = Int32(outputTensor.count) + cblas_scopy(count32, inputPointer, 1, outputPointer, 1) + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "LinearOperator" + let kernelLabel = "Linear" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } +} + +/** +Exponential Linear Units. + +`y = x if x > 0, else y = alpha*(exp(x) - 1)` + +## Reference +[Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)](https://arxiv.org/abs/1511.07289) +*/ +public class ELUOperator: ActivationOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// hyperparameter, default is `1.0` + public var alpha:Float = 1.0 + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + print("NOT USE") // will override cpu(). this block will not be used + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "ELUOperator" + let kernelLabel = "ELU" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + + /// Initial by setting `alpha` value + /// + /// - Parameters: + /// - computationDelegate: computationDelegate description + /// - alpha: alpha description + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, alpha: Float) { + self.init(computationDelegate: computationDelegate) + self.alpha = alpha + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Attribute `alpha` as a `ScalarSymbol`. + /// + /// - Returns: Array of GraphSymbol + public override func paramSymbols() -> [GraphSymbol] { + let alpha = SerranoScalarSymbol("alpha", dataType: .float, dataSource: .Default) + alpha.bindedData = Float(1.0) + return [alpha] + } + + /// Override CPU + internal override func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0.. 0.0 { outputReadre[i] = inputReader[i] } + else { outputReadre[i] = self.alpha * (exp(inputReader[i]) - 1.0) } + } + workGroup.leave() + } + } + + workGroup.wait() + } + + /// Override GPU calculation cause there's a hyperparameter to pass-in + internal override func gpu() { + // prepare resource + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffer: MTLCommandBuffer? + var inputBuffers: [MTLBufferResource] = [MTLBufferResource]() + var resultBuffers: [MTLBufferResource] = [MTLBufferResource]() + var countBuffers: [MTLBuffer] = [MTLBuffer]() + var alphaBuffer: MTLBuffer? + + //// kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + //// command buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + commandBuffer = engine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + resourcePrepareGroup.leave() + } + + // prepare tensor MTLBuffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + inputBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources(self.inputTensors!) + resultBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources(self.outputTensors!) + resourcePrepareGroup.leave() + } + + // count buffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + for tensor in self.outputTensors! { + var count = UInt32(tensor.count) + let countBuffer = engine.GPUDevice?.makeBuffer(bytes: &count, + length: MemoryLayout.size) + guard countBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + countBuffers.append(countBuffer!) + + } + resourcePrepareGroup.leave() + } + + // alpha buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + alphaBuffer = engine.GPUDevice?.makeBuffer(bytes: &self.alpha, + length: MemoryLayout.size) + guard alphaBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(alphaBuffer!.length) bytes] requested for alpha value \(self.alpha) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + resourcePrepareGroup.leave() + } + resourcePrepareGroup.wait() + + + + for bufferIndex in 0.. Void in + print("NOT USE") + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "SELUOperator" + let kernelLabel = "SELU" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + /// Initial by setting `alpha` value and `scale` values. + /// + /// - Parameters: + /// - computationDelegate: computationDelegate description + /// - alpha: alpha description + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, alpha: Float, scale:Float) { + self.init(computationDelegate: computationDelegate) + self.alpha = alpha + self.scale = scale + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Attribute `alpha` as a `ScalarSymbol`. + /// Attribute `scale` as a `ScalarSymbol` + /// + /// - Returns: Array of GraphSymbol + public override func paramSymbols() -> [GraphSymbol] { + let alpha = SerranoScalarSymbol("alpha", dataType: .float, dataSource: .Default) + alpha.bindedData = Float(1.673263) + + let scale = SerranoScalarSymbol("scale", dataType: .float, dataSource: .Default) + alpha.bindedData = Float(1.050701) + + return [alpha, scale] + } + + /// Override CPU + internal override func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0.. 0.0 { outputReadre[i] = inputReader[i] } + else { outputReadre[i] = self.alpha * (exp(inputReader[i]) - 1.0) } + outputReadre[i] *= self.scale + } + workGroup.leave() + } + } + + workGroup.wait() + } + + /// Override GPU calculation cause there's a hyperparameter to pass-in + internal override func gpu() { + // prepare resource + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffer: MTLCommandBuffer? + var inputBuffers: [MTLBufferResource] = [MTLBufferResource]() + var resultBuffers: [MTLBufferResource] = [MTLBufferResource]() + var countBuffers: [MTLBuffer] = [MTLBuffer]() + var alphaBuffer: MTLBuffer? + var scaleBuffer: MTLBuffer? + + //// kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + //// command buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + commandBuffer = engine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + resourcePrepareGroup.leave() + } + + // prepare tensor MTLBuffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + inputBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources(self.inputTensors!) + resultBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources(self.outputTensors!) + resourcePrepareGroup.leave() + } + + // count buffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + for tensor in self.outputTensors! { + var count = UInt32(tensor.count) + let countBuffer = engine.GPUDevice?.makeBuffer(bytes: &count, + length: MemoryLayout.size) + guard countBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + countBuffers.append(countBuffer!) + + } + resourcePrepareGroup.leave() + } + + // alpha buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + alphaBuffer = engine.GPUDevice?.makeBuffer(bytes: &self.alpha, + length: MemoryLayout.size) + guard alphaBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(alphaBuffer!.length) bytes] requested for alpha value \(self.alpha) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + resourcePrepareGroup.leave() + } + resourcePrepareGroup.wait() + + // scalue buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + scaleBuffer = engine.GPUDevice?.makeBuffer(bytes: &self.scale, + length: MemoryLayout.size) + guard scaleBuffer != nil else { + SerranoLogging.errorLogging(message: " Failed to careate MTLBuffer.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(scaleBuffer!.length) bytes] requested for alpha value \(self.scale) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + resourcePrepareGroup.leave() + } + resourcePrepareGroup.wait() + + + for bufferIndex in 0..=0`. + /// Any negative value will be automatically making this attribute value to `-1`. + /// - Note: `-1` is a special value indicating last dim. + public var dim: Int = -1 { + didSet { + if self.dim < 0 { self.dim = -1 } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + print("NOT USE") + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "SoftmaxOperator" + let kernelLabel = "" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + } + + /// Initial by setting `dim` value. + /// + /// - Parameters: + /// - computationDelegate: computationDelegate description + /// - dim: reduce dim + public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, dim: Int) { + self.init(computationDelegate: computationDelegate) + self.dim = dim + if self.dim < 0 { self.dim = -1 } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Override this function from `UnaryOperaotr` to do additional checking validation bewtween `dim` and `shapes`. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public override func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // super check + let outShapes = super.outputShape(shapeArray: shapes) + + // check dim and shapes + for shape in shapes { + guard self.dim < shape.shapeArray.count else { + SerranoLogging.errorLogging(message: "Invalid shape [\(shape.description)] for dim \(self.dim).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + } + return outShapes + } + + /// Override computaion methods. + /// This computation just call other operators. + /// + /// - Parameter computationMode: computationMode + public override func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) calculation aborted cause invalid input tensors or output tensors: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + + self.computationDelegate?.operatorWillBeginComputation(self) + + let computeGroup = DispatchGroup() + for tensorIndex in 0.. [GraphSymbol] { + let dim = SerranoScalarSymbol("dim", dataType: .int, dataSource: .Default) + dim.bindedData = -1 + + return [dim] + } +} + +/** +LeakyReLU activation operator. +``` +y = alpha * x (x < 0) +y = x (x >= 0) +``` + +## scale factor alpha +Default value is `0.3` + +## Reference +[Rectifier Nonlinearities Improve Neural Network Acoustic Models](https://web.stanford.edu/~awni/papers/ReLU_hybrid_icml2013_final.pdf) +*/ +public class LeakyReLUOperator: ELUOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + print("NOT USE") // will override cpu(). this block will not be used + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "LeakyReLUOperator" + let kernelLabel = "LeakyReLU" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + self.alpha = 0.3 + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Override CPU + internal override func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0..= 0.0 { outputReadre[i] = inputReader[i] } + else { outputReadre[i] = self.alpha * inputReader[i] } + } + workGroup.leave() + } + } + workGroup.wait() + } + +} + + +/** +Thresholded Rectified Linear Unit. +`f(x) = x if x > alpha, else f(x) = 0` + +## Threshold `alpha` +Default value is `1.0` + +## Reference +[Zero-Bias Autoencoders and the Benefits of Co-Adapting Features](https://arxiv.org/abs/1402.3337) +*/ +public class ThresholdedReLUOperator: ELUOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializer + + /// Convenience initializer + /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` + /// + /// - Parameter computationDelegate: computationDelegate + required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { + let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in + print("NOT USE") // will override cpu(). this block will not be used + } + let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in + //TODO: implemented + fatalError("Not implemented") + } + let defaultLabel = "ThresholdedReLUOperator" + let kernelLabel = "ThresholdedReLU" + self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, + metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, + inputTensors: nil, outputTensors: nil) + self.alpha = 1.0 + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Override CPU + internal override func cpu() { + let workGroup = DispatchGroup() + for tensorIndex in 0.. self.alpha { outputReadre[i] = inputReader[i] } + else { outputReadre[i] = 0.0 } + } + workGroup.leave() + } + } + workGroup.wait() + } +} + +/** +Parametric Rectified Linear Unit. + +``` +y_i = alpha_i * x_i (x_i< 0) +y_i = x_i (x_i >= 0) +``` + +## Reference +[Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification](https://arxiv.org/abs/1502.01852) +*/ +//public class PReLUOperator: ActivationOperator { + //TODO: RE-IMPLEMENTATION + +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // MARK: - Attributes +// +// +// /// A tensor +// /// - Note: If this attribute is `nil` when doing calculation, +// /// operator will use `defaultAlpha` as the default values. +// public var alpha: Tensor +// +// /// Default alpha values when `alpha` is `nil` +// public var defaultAlphaValue: Float = 0.3 +// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // MARK: - Initializers +// +// /// Convenience initializer +// /// Subclass required to override this function to assign `cpuComputeBlock` and `metalKernelFuncLabel` +// /// +// /// - Parameter computationDelegate: computationDelegate +// required public convenience init(computationDelegate: OperatorCalculationDelegate? = nil) { +// let block = { (inputTensor: Tensor, outputTensor: Tensor) -> Void in +// print("NOT USE") // will override cpu(). this block will not be used +// } +// let gradBlock = { (inputs: [Tensor], mode: OperatorComputationMode) -> [DataSymbolSupportedDataType] in +// //TODO: implemented +// fatalError("Not implemented") +// } +// let defaultLabel = "PReLUOperator" +// let kernelLabel = "PReLU" +// self.init(operatorLabel: defaultLabel, cpuComputeBlock: block, gradComputationBlock: gradBlock, +// metalKernelFuncLabel: kernelLabel, computationDelegate: computationDelegate, +// inputTensors: nil, outputTensors: nil) +// self.alpha = nil +// } +// +// /// Initial by setting `alpha` value +// /// +// /// - Parameters: +// /// - computationDelegate: computationDelegate +// /// - alpha: alpha tensor object +// public convenience init(computationDelegate: OperatorCalculationDelegate? = nil, alpha: [Tensor]) { +// self.init(computationDelegate: computationDelegate) +// self.alpha = alpha +// } +// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// // MARK: - Methods +// +// /// Additional checking needs to verify the diemsions mathcing of `alpha` and `inputTensors`. +// /// +// /// - Note: If `alpha` is `nil`, this function will generate default `alpha` following `defaultAlphaValue`. +// /// +// /// - Returns: return pass or not and error message. +// public override func inputOutputTensorsCheck() -> (check: Bool, msg: String) { +// // super check +// let (pass, msg) = super.inputOutputTensorsCheck() +// guard pass else { +// // false return +// return (pass, msg) +// } +// +// // check alpha +// if self.alpha == nil { +// // generate default alpha +// SerranoLogging.stdLogging(message: "Operator \(self.operatorLabel) has nil alpha. Will generate default alpha tensors with value \(self.defaultAlphaValue).", +// file: "\(#file)", function: "\(#function)", line: "\(#line)", +// loggingLevel: SerranoLoggingType.LowLevel) +// self.alpha = [Tensor]() +// for tensor in self.inputTensors! { +// self.alpha!.append(Tensor(repeatingValue: self.defaultAlphaValue, tensorShape: tensor.shape)) +// } +// } else { +// // check matching +// guard self.alpha!.count == self.inputTensors!.count else { +// return (false, "Attribute alpha does not have same number of tensor objects as input tensors does.") +// } +// for (alphaTensor, inputTensor) in zip(self.alpha!, self.inputTensors!) { +// guard alphaTensor.shape .== inputTensor.shape else { +// return (false, "Alpha has shape \(alphaTensor.shape.description) while input tensor has shape \(inputTensor.shape.description).") +// } +// } +// } +// +// return (true, "") +// } +// +// /// Attribute `alpha` as an array of `TensorSymbol` if not `nil`. +// /// Attribute `defaultAlphaValue` as a `ScalarSymbol`. +// /// +// /// - Returns: Array of GraphSymbol +// public override func paramSymbols() -> [GraphSymbol] { +// let defaultAlphaValue = SerranoScalarSymbol("defaultAlphaValue", dataType: .float, dataSource: .Default) +// defaultAlphaValue.bindedData = Float(0.3) +// +// var symbols = [defaultAlphaValue] +// +// if self.alpha != nil { +// for tensor in self.alpha! { +// symbols.append(SerranoTensorSymbol( ) +// } +// } +// +// return symbols +// } +// +// /// Override +// internal override func cpu() { +// let workGroup = DispatchGroup() +// for i in 0...size) +// guard countBuffer != nil else { +// fatalError("[Serrano] Failed to careate MTLBuffer.") +// } +// SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(countBuffer!.length) bytes] requested for count info \(count) by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) +// +// // encoder +// let encoder = commandBuffer!.makeComputeCommandEncoder() +// encoder.setComputePipelineState(kernel!) +// encoder.setBuffer(inputBuffers[bufferIndex].buffer, offset: inputBuffers[bufferIndex].offset, at: 0) +// encoder.setBuffer(resultBuffers[bufferIndex].buffer, offset: resultBuffers[bufferIndex].offset, at: 1) +// encoder.setBuffer(countBuffer, offset: 0, at: 2) +// encoder.setBuffer(alphaBuffers[bufferIndex].buffer, offset: alphaBuffers[bufferIndex].offset, at: 3) +// +// // dispatch +// let threadsPerThreadgroup = MTLSizeMake(kernel!.threadExecutionWidth, +// 1, +// 1) +// let threadgroupsPerGrid = MTLSizeMake((self.inputTensors![bufferIndex].count + threadsPerThreadgroup.width - 1) / threadsPerThreadgroup.width, +// 1, +// 1) +// encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) +// SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) +// encoder.endEncoding() +// } +// +// // commit command buffer +// commandBuffer!.commit() +// commandBuffer!.waitUntilCompleted() +// } +//} + diff --git a/Source/Serrano/operators/nn/common/batchnorm_op.swift b/Source/Serrano/operators/nn/common/batchnorm_op.swift new file mode 100644 index 0000000..bae0590 --- /dev/null +++ b/Source/Serrano/operators/nn/common/batchnorm_op.swift @@ -0,0 +1,23 @@ +// +// batchnorm_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/14/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + +//TODO: Implementation + +///** +//Batch normalization operator. +// +//Normalize on input tensors by each tensor's mean and variance. +//*/ +//public class BatchNormOperator: ComputableOperator { +// +//} diff --git a/Source/Serrano/operators/nn/common/fullyconnected_op.metal b/Source/Serrano/operators/nn/common/fullyconnected_op.metal new file mode 100644 index 0000000..e5fa30c --- /dev/null +++ b/Source/Serrano/operators/nn/common/fullyconnected_op.metal @@ -0,0 +1,77 @@ +// +// fullyconnected_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 7/19/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +typedef struct +{ + uint M; // number of rows in A + uint N; // number of cols in B + uint K; // number of cols in A, number of rows in B + ushort stride; // Element stride in bytes + bool useBias; // 0 - false, 1 - true +} FCInfo; + + +kernel void Fullyconnected(constant float* input_A [[ buffer(0) ]], + constant float* input_B [[ buffer(1) ]], // should be transposed + const device float* C [[ buffer(2) ]], + constant float* biasArray [[ buffer(3) ]], + constant FCInfo& info [[ buffer(4) ]], + uint2 gid [[ thread_position_in_grid ]]) { + uint M = info.M; + uint N = info.N; + uint K = info.K; + ushort stride = info.stride; + bool useBias = info.useBias; + + // check boundary + if (gid.x >= M || gid.y >= N) return; + + device float* c_reader = (device float*)((device char*)C + gid.x * N * stride + gid.y * stride); + c_reader[0] = (useBias ? biasArray[gid.y] : 0.0f); + + // small case + if (K < 4) { + constant float* a_reader = (constant float*)((constant char*)input_A + gid.x * K * stride); + constant float* b_reader = (constant float*)((constant char*)input_B + gid.y * K * stride); + ushort i = 0; + while (i < K) { + c_reader[0] += a_reader[i] * b_reader[i]; + i++; + } + return; + } + + // regular case, each time read 4 elements + constant float4* a_reader = (constant float4*)((constant char*)input_A + gid.x * K * stride); + constant float4* b_reader = (constant float4*)((constant char*)input_B + gid.y * K * stride); + uint align_bound = K / 4; + ushort i = 0; + float4 result; + while (i < align_bound) { + result = a_reader[i] * b_reader[i]; + c_reader[0] += result.x; + c_reader[0] += result.y; + c_reader[0] += result.z; + c_reader[0] += result.w; + i++; + } + + // rest + if (K % 4 != 0) { + constant float* a_reader_p = (constant float*)((constant char*)input_A + gid.x * K * stride); + constant float* b_reader_p = (constant float*)((constant char*)input_B + gid.y * K * stride); + i = align_bound * 4; + while (i < K) { + c_reader[0] += a_reader_p[i] * b_reader_p[i]; + i++; + } + } +} diff --git a/Source/Serrano/operators/nn/common/fullyconnected_op.swift b/Source/Serrano/operators/nn/common/fullyconnected_op.swift new file mode 100644 index 0000000..867081e --- /dev/null +++ b/Source/Serrano/operators/nn/common/fullyconnected_op.swift @@ -0,0 +1,574 @@ +// +// fullyconnected_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + +/// This struct corresponds to the `FCInfo` struct in file 'fullyconnected_op.metal' +public struct FCInfo { + var M: MetalUInt // number of rows in A + var N: MetalUInt // number of cols in B + var K: MetalUInt // number of cols in A, number of rows in B + var stride: MetalUShort // element stride in bytes + var useBias: Bool // 1 - true, 0 - false +} + +/** +The regular fully connected operator. +Operator do the `1D_dot(inputTensor.flatten, weights) + bias` calculation on all input tensors. + +## Weight tensor layout +The `weight` is a 2D tensor with shape: `[n, m]` where : +- `n`: the flattened dimension of `inputTensors`, i.e. same value as `count` of a input tensor. +- `m`: the number of hidden nodes, same value as `numUnits`; + +Thus, each column stores the weights of corresponding hidden unit. + +## Bias tensor layout +The `bias` is a 1D tensor with shape `[m]` where: +- `m`: the number of hidden nodes, same value as `numUnits`; + +Thus, each value in the tensor is the bias value of corresponding hidden unit. + +## Input tensors auto flatten +For input tensor with rank `>=2`, the operator will automatically flatten the tensor and then do calcualtion. + +## Multiple input tensors +If `inputTensors` has more than 1 tensor object, +the operator applies calculation on each input tensor independently +and stores the results in corresponding tensor of `outputTensors`. + +- Note: All input tensor should have same `count`. + +## Bias enable choice +Bias could be disabled by setting the `biasEnabled` to `false`. + +## Batch calculation +This operator itself does not explicitly support batch calculation. +But user can use slice tensor to do the same thing. +Details can be found in [Slice tensor]() and [Batch calculation with operators]() +*/ +public class FullyconnectedOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String = "Fullyconnected" + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// If use `bias`. Default is `true`. + public var biasEnabled: Bool = true + + /// Weight tensor. + /// + /// ## Shape specific + /// The tensor should be with shape `[inputDim, numUnits]`. + /// + /// - Note: If `weight` is `nil` when calculation, `fataError` will be raised. + public var weight: Tensor? + + /// Bias tensor + /// + /// ## Shape specific + /// The tensor should be with shape `[numUnits]`. + /// + /// - Note: If `bias` is `nil` when calculation, `fataError` will be raised. + public var bias: Tensor? + + /// Number of input units. Must be a positive integer. + public var inputDim: Int = 1 { + didSet { + if numUnits <= 0 { + SerranoLogging.errorLogging(message: "Attribute inputDim of FullyconnectedOperator must be a positive integer. " + + "Given \(numUnits).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + } + + /// Number of hidden units. Must be a positive integer. + public var numUnits: Int = 1 { + didSet { + if numUnits <= 0 { + SerranoLogging.errorLogging(message: "Attribute numUnits of FullyconnectedOperator must be a positive integer. " + + "Given \(numUnits).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + } + + /// If `true`, operator will not check the `upGrads`'s shape. + /// This is used inside framework to speed up in situation we know it will not be wrong. + /// Cases like auto generated differentiation graph. + public var disableUpGradShapeCheck: Bool = false + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = true + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Designated init + /// + /// - Parameters: + /// - inputDim: inputDim + /// - numUnits: numUnits + /// - operatorLabel: operatorLabel + /// - inputTensors: inputTensors + /// - outputTensors: outputTensors + /// - computationDelegate: computationDelegate + /// - weight: weight + /// - bias: bias + public init(inputDim: Int, numUnits: Int, operatorLabel: String, inputTensors: [Tensor]?, outputTensors: [Tensor]?, + computationDelegate: OperatorCalculationDelegate?, weight: Tensor?, bias: Tensor?) { + guard numUnits >= 1 else { + SerranoLogging.errorLogging(message: "Attribute numUnits of FullyconnectedOperator must be a positive integer. " + + "Given \(numUnits).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + guard inputDim >= 1 else { + SerranoLogging.errorLogging(message: "Attribute inputDim of FullyconnectedOperator must be a positive integer. " + + "Given \(numUnits).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + self.inputDim = inputDim + self.numUnits = numUnits + self.operatorLabel = operatorLabel + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.computationDelegate = computationDelegate + self.weight = weight + self.bias = bias + } + + /// Convenience init + /// + /// - Parameters: + /// - inputDim: inputDim + /// - numUnits: numUnits + /// - inputTensors: inputTensors + /// - outputTensors: outputTensors + /// - computationDelegate: computationDelegate + public convenience init(inputDim: Int, numUnits: Int, + operatorLabel: String = "FullyconnectedOperator", + inputTensors: [Tensor]? = nil, outputTensors: [Tensor]? = nil, + computationDelegate: OperatorCalculationDelegate? = nil) { + self.init(inputDim: inputDim, numUnits: numUnits, operatorLabel: operatorLabel, + inputTensors: inputTensors, outputTensors: outputTensors, + computationDelegate: computationDelegate, weight: nil, bias: nil) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// The output shape of `FullyConnectedOperator` is decides by `numUnits` and `inputDim`. + /// + /// - Parameter shapes: input shapes + /// - Returns: result shapes + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // empty check + guard shapes.count >= 1 else { + SerranoLogging.errorLogging(message: "Input shapes array is empty.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // all shapes should equal to inputDim + for shape in shapes { + guard self.inputDim == shape.count else { + SerranoLogging.errorLogging(message: "Input shape's count(\(shape.count)) should equal to inputDim(\(self.inputDim)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + } + + var outShapes = [TensorShape]() + for shape in shapes { + outShapes.append(TensorShape(dataType: shape.dataType, shape: [self.numUnits])) + } + return outShapes + } + + + /// Check validation of `inputTensors`, `outputTensors`, `weight` and `bias`. + /// + /// - Returns: check, if pass. msg, error message. + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input tensor not nil + guard self.inputTensors != nil else { + return (false, "Input tensors array is nil.") + } + + // output tensor not nil + guard self.outputTensors != nil else { + return (false, "Output tensors array is nil.") + } + + // weight not nil + guard self.weight != nil else { + return (false, "Weight tensor is nil.") + } + + // bias not nil if enabled + if self.biasEnabled { + guard self.bias != nil else { + return (false, "Bias is nil.") + } + } + + // check input shapes + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapesCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapesCheck != nil else { + return (false, "Input tensors are invalid. Check log for details.") + } + + // check output shape + let outputShapes = self.outputTensors!.map { $0.shape } + guard outputShapes.count == inputShapes.count else { + // same count + return (false, "Input tensors and output tensors have different number of objects. " + + "Input tensors contains \(inputShapes.count) tensors, output tensor contains \(outputShapes.count) tensors.") + } + for (shape, shapeCheck) in zip(outputShapes, outputShapesCheck!) { + // same shape + guard shape == shapeCheck else { + return (false, "One of output tensors has invalid shape. Expect \(shapeCheck.description), give \(shape.description).") + } + } + + // check weights dim + guard self.weight!.rank == 2 else { + // rank + return (false, "Weight tensor has invalid rank. Expect 2, given \(self.weight!.rank).") + } + guard self.weight!.shape.shapeArray[0] == self.inputDim && self.weight!.shape.shapeArray[1] == self.numUnits else { + // match with input + return (false, "Weight shape is invalid. Expect [\(self.inputDim), \(self.numUnits)], given \(self.weight!.shape.shapeArray).") + } + + // check bias dim + if self.biasEnabled { + guard self.bias!.rank == 1 else { + // rank + return (false, "Bias tensor has invalid rank. Expect 1, given \(self.bias!.rank).") + } + guard self.bias!.shape.shapeArray[0] == self.numUnits else { + // shape match with numUnits + return (false, "Bias tensor shape is invalid. Expect [\(self.numUnits)], given [\(self.bias!.shape.shapeArray[0])].") + } + } + + return (true, "") + } + + /// Compute synclly. + /// + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: cmputation mode. If choose `GPU` but haven't configued a GPU SerranoEngine, operator will use `CPU` to compute. + /// - Returns: result tensors + public func compute(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) aborts calculation cause given invalid data: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + /// Compute asynclly + /// + /// - Parameters: + /// - tensors: input tensors + /// - computationMode: computation mode + public func computeAsync(_ computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + //TODO: Implementation + fatalError("Not implemented") + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + _ = self.gradCompute(computationMode) + } + } + + /// Update params if possible. + /// No update parameters for binary operators. + /// + /// - Parameters: + /// - grads: grads tensor list + /// - LR: learning rate + public func updateParams(grads: [Tensor], LR: Float) { + return + } + + /// Bind according to labels. + /// + /// -Note: if cannot bind all needed parameters. `fatalError` will be raised. + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + var paramsLabels = ["weight"] + if self.biasEnabled { + paramsLabels.append("bias") + } + + for label in paramsLabels { + let symbol = (symbols.filter {$0.symbolLabel == label}).first + guard symbol != nil else{ + SerranoLogging.errorLogging(message: "\(label) symbol does not exist.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + guard symbol!.symbolType == SymbolType.Tensor else { + SerranoLogging.errorLogging(message: "\(label) symbol should be a tensor symbol.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + let dataSymbol = symbol! as! TensorSymbol + guard dataSymbol.bindedData != nil else { + SerranoLogging.errorLogging(message: "\(label) symbol has no binded data.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + if label == "weight" { + self.weight = dataSymbol.bindedData! as! Tensor + } else { + self.bias = dataSymbol.bindedData! as! Tensor + } + } + } + + /// Attribute `weight` as a `TensorSymbol`. + /// Attribute `bias` as a `TensorSymbol`. + /// + /// - Returns: Array of GraphSymbol + public func paramSymbols() -> [GraphSymbol] { + // These labels are important for bindParamSymbols(:) + let weight = SerranoTensorSymbol("weight", dataSource: .User, shape: TensorShape(dataType: .float, shape: [self.inputDim, self.numUnits])) + let bias = SerranoTensorSymbol("bias", dataSource: .User, shape: TensorShape(dataType: .float, shape: [self.numUnits])) + + return [weight, bias] + } + + /// Just do the matrix multiplication between each inpute tensor and weight tensor. + /// Then add bias. + internal func cpu() { + let weightAddress = self.weight!.contentsAddress + let biasAddress = self.bias?.contentsAddress + + for (inputTensor, outputTensor) in zip(self.inputTensors!, self.outputTensors!) { + let inputAddress = inputTensor.contentsAddress + let outputAddress = outputTensor.contentsAddress + + // weights + let M = Int32(1) + let N = Int32(self.weight!.shape.shapeArray[1]) + let K = Int32(inputTensor.count) + + let startTime = CFAbsoluteTimeGetCurrent() + + cblas_sgemm(CblasRowMajor, cblasTrans(false), cblasTrans(false), M, N, K, + 1.0, inputAddress, K, weightAddress, N, 0.0, outputAddress, N) + + // bias + if self.biasEnabled { + let count = vDSP_Length(outputTensor.count) + vDSP_vadd(outputAddress, 1, biasAddress!, 1, outputAddress, 1, count) + } + + let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime + SerranoLogging.stdLogging(message: "Executed CPU calcualtion in \(timeElapsed) seconds.", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + + } + } + + /// We use op + internal func gpu() { + let workGroup = DispatchGroup() + let weightTensor = self.weight! + + // cal for each tensor + for (inputTensor, outputTensor) in zip(self.inputTensors!, self.outputTensors!) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + + // transpose weight + let transposeWeight = SerranoResourceManager.globalManager.allocateUnamangedTensor(weightTensor.shape.transposed()) + let transOp = TransposeOperator(inputTensors: [weightTensor], outputTensors: [transposeWeight]) + transOp.compute(.GPU) + + // prepare resources + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffer: MTLCommandBuffer? + var dataBuffers: [MTLBufferResource] = [MTLBufferResource]() + var infoBuffer: MTLBuffer? + + // kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + // command buffer + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + commandBuffer = engine.serranoCommandQueue?.makeCommandBuffer() + guard commandBuffer != nil else { + fatalError("[Serrano] Failed to make new command buffer.") + } + resourcePrepareGroup.leave() + } + + //// Prepare MTLBuffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + if self.biasEnabled { + dataBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources([inputTensor, transposeWeight, + outputTensor, self.bias!]) + } else { + dataBuffers = SerranoResourceManager.globalManager.allocateMTLBufferResources([inputTensor, transposeWeight, + outputTensor]) + } + resourcePrepareGroup.leave() + } + + /// FCInfo buffer + /// Here, no matter what shape the input tensor has, we view it as a [1, count] matrix. + var info = FCInfo(M: MetalUInt(1), + N: MetalUInt(weightTensor.shape.shapeArray[1]), + K: MetalUInt(inputTensor.count), + stride: MetalUShort(MemoryLayout.stride), + useBias: self.biasEnabled) + infoBuffer = engine.GPUDevice?.makeBuffer(bytes: &info, length: MemoryLayout.stride, options: .storageModeShared) + guard infoBuffer != nil else { fatalError("[Serrano] Failed to careate MTLBuffer.") } + SerranoLogging.stdLogging(message: "Allocated a Metal buffer [\(infoBuffer!.length) bytes] requested for matrix dimentsion info info \(info) by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + resourcePrepareGroup.wait() + + //// Encoders. + let encoder = commandBuffer!.makeComputeCommandEncoder() + encoder.setComputePipelineState(kernel!) + encoder.setBuffer(dataBuffers[0].buffer, offset: dataBuffers[0].offset, at: 0) + encoder.setBuffer(dataBuffers[1].buffer, offset: dataBuffers[1].offset, at: 1) + encoder.setBuffer(dataBuffers[2].buffer, offset: dataBuffers[2].offset, at: 2) + if self.biasEnabled { encoder.setBuffer(dataBuffers[3].buffer, offset: dataBuffers[3].offset, at: 3) } + encoder.setBuffer(infoBuffer, offset: 0, at: 4) + /// Calculate grid + let threadsPerThreadgroup = MTLSizeMake(1, + kernel!.threadExecutionWidth, + 1) + /// virew output tensor as a [1, numUnit] matrix + let threadgroupsPerGrid = MTLSizeMake(1, + (outputTensor.shape.shapeArray[0] + threadsPerThreadgroup.height - 1) / threadsPerThreadgroup.height, + 1) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + encoder.endEncoding() + + // commit command buffer + commandBuffer!.commit() + + let startTime = CFAbsoluteTimeGetCurrent() + commandBuffer!.waitUntilCompleted() + let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime + SerranoLogging.stdLogging(message: "Executed GPU calcualtion in \(timeElapsed) seconds.", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + + workGroup.leave() + } + } + + workGroup.wait() + } + +} diff --git a/Source/Serrano/operators/nn/convolution/conv_op.metal b/Source/Serrano/operators/nn/convolution/conv_op.metal new file mode 100644 index 0000000..bbf4d38 --- /dev/null +++ b/Source/Serrano/operators/nn/convolution/conv_op.metal @@ -0,0 +1,27 @@ +// +// conv_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 10/26/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +/** + Naive convolutional 2D. + Each thread calculate one value in output tensor. + */ + +//TODO: implement + + +//void kernel conv2d_naive(constant float* input [[ buffer(0) ]], +// device float* output [[ buffer(1) ]], +// constant Img2ColInfo& info [[ buffer(2) ]], +// uint3 group_id [[ threadgroup_position_in_grid ]], +// uint3 thread_id_group [[ thread_position_in_threadgroup ]]) { +// +//} + diff --git a/Source/Serrano/operators/nn/convolution/conv_op.swift b/Source/Serrano/operators/nn/convolution/conv_op.swift new file mode 100644 index 0000000..6d87199 --- /dev/null +++ b/Source/Serrano/operators/nn/convolution/conv_op.swift @@ -0,0 +1,476 @@ +// +// conv_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/14/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + +// TODO: figure out if needs inputshape for same on all inputs + +/** +2D Convolution operator. + +## Input tensors shapes +All tensors in `inputTensors` should have same shapes + +## Shape specification +- inputTensors: each tensor: `[channel, height, width]` or `[height, width, channel]` according to `channelPosition` +- weight: `[num_filter,channel, kernelSize[0], kernelSize[1]]`, +- outputTensors: each tensor: `[out_height, out_width, num_filter]`, i.e., `TensorChannelOrder.Last` + +## nil weight +At declaration, `weight` could be `nil`. +However, if you add this operator through a graph's `operation(_,inputs:,op:)` API (i.e. symbolic constructing graph). +You must indicate the `inputShape` attribute so that the graph could estimate the input and output shape information. + +## Batch process +This operator does not directly support batch data. +However, user could use `TensorSlice` to do the batch processing. +Details can be found in [Slice tensor]() and [Batch calculation with operators](). + +## Dilation +Currently, calculation with `dilation > 1` has not been implemented and supported. + +*/ +public class ConvOperator2D: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String = "Conv2D" + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = true + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + /// The number of fitlers + public var numFilters: Int + + /// The kernel size. `[height, width]` + public var kernelSize: [Int] + + /// Stride. `[height, width]` + /// Default is `[1, 1]`. + public var stride: [Int] + + /// Dilate values. 2D vecotr indicating the dilation value in height and width. + /// Default is `[1, 1]`, i.e. without dilation. + /// TODO: Support dilation + public var dilation: [Int] + + /// Padding mode. Default `PaddingMode.valid` + public var padMode: PaddingMode = PaddingMode.Valid + + /// Channel position. Default is `ImageChannelOrder.First` + public var channelPosition: TensorChannelOrder = .First + + /// The weight tensor. + public var weight: Tensor? + + /// The input shape + /// Used to indicate the input tensors' shape. + /// Should not be `nil` construction from scratch. + public var inputShape: TensorShape? + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Init + + /// Designated init. + /// + /// - Parameters: + /// - numFilters: + /// - kernelSize: + /// - stride: + /// - padMode: + /// - channelPosition: + /// - weight: + /// - dilation: + /// - computationDelegate: + /// - inputTensors: + /// - outputTensors: + /// - operatorLabel: + /// - inputShape: + /// - disableInputOutputCheck: + public init(numFilters: Int, kernelSize: [Int], + stride: [Int] = [1, 1], + padMode: PaddingMode = .Valid, + channelPosition: TensorChannelOrder = .First, + weight: Tensor? = nil, + dilation: [Int] = [1, 1], + computationDelegate: OperatorCalculationDelegate? = nil, + inputTensors: [Tensor]? = nil, outputTensors: [Tensor]? = nil, + operatorLabel: String = "Conv2DOp", + inputShape: TensorShape? = nil, + disableInputOutputCheck: Bool = false) { + self.numFilters = numFilters + self.kernelSize = kernelSize + self.stride = stride + self.dilation = dilation + self.padMode = padMode + self.channelPosition = channelPosition + self.weight = weight + self.computationDelegate = computationDelegate + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.operatorLabel = operatorLabel + self.inputShape = inputShape + self.disableInputOutputCheck = disableInputOutputCheck + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Compute output shape according `numFilters`, `kernelSize`, `stride` and `dilation`. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // shape empty check + guard shapes.count >= 1 else { + SerranoLogging.errorLogging(message: "Input shapes array is empty", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // numFilters check + guard numFilters > 0 else { + SerranoLogging.errorLogging(message: "numFilters (\(self.numFilters)) should be a positive integer", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // kernelSize check + guard self.kernelSize.count == 2 && self.kernelSize[0] > 0 && self.kernelSize[1] > 0 else { + SerranoLogging.errorLogging(message: "Invalid kernelSize (\(self.kernelSize)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // stride check + guard self.stride.count == 2 && self.stride[0] > 0 && self.stride[1] > 0 else { + SerranoLogging.errorLogging(message: "Invalid stride (\(self.stride)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // dilation check + guard self.dilation.count == 2 && self.dilation[0] >= 1 && self.dilation[1] >= 1 else { + SerranoLogging.errorLogging(message: "Invalid dilation (\(self.dilation)).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // input shapes same check + let checkShape = shapes.first! + for shape in shapes { + guard shape == checkShape else { + SerranoLogging.errorLogging(message: "Input shapes should have same shape.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + } + + var outputShapes = [TensorShape]() + for inShape in shapes { + // check input shapes + let shapeArray = inShape.shapeArray + guard inShape.rank == 3 || shapeArray[0] > 0 || shapeArray[1] > 0 || shapeArray[2] > 0 else { + SerranoLogging.errorLogging(message: "Invalid input shape \(inShape.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + let (_, height, width) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: shapeArray) + + if self.dilation[0] >= 2 || self.dilation[1] >= 2 { + fatalError("Not implemented") + } else { + let outShapeArray = [kernelScanningOutSize(self.padMode, inputSize: height, + kernelSize: self.kernelSize[0], stride: self.stride[0]), + kernelScanningOutSize(self.padMode, inputSize: width, + kernelSize: self.kernelSize[1], stride: self.stride[1]), + self.numFilters] + outputShapes.append(TensorShape(dataType: inShape.dataType, shape: outShapeArray)) + } + } + return outputShapes + } + + + /// Check input and output tensors. + /// + /// - Returns: return value description + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input nil check + guard self.inputTensors != nil && self.inputTensors!.count > 0 else { + return (false, "inputTensors is nil or empty.") + } + + // output nil check + guard self.outputTensors != nil && self.outputTensors!.count > 0 else { + return (false, "outputTensors is nil or empty.") + } + + guard self.weight != nil else { + return (false, "weight is nil.") + } + + // weight shape check + // [channel, kernelSize[0], kernelSize[1], num_filter] + let weightShapeArray = self.weight!.shape.shapeArray + let (channel, _, _) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: self.inputTensors!.first!.shape.shapeArray) + guard weightShapeArray.count == 4 && weightShapeArray[0] == self.numFilters + && weightShapeArray[2] == self.kernelSize[0] && weightShapeArray[3] == self.kernelSize[1] + && weightShapeArray[1] == channel else { + return (false, "Invalid weight shape, Expect \([self.numFilters, channel, self.kernelSize[0], self.kernelSize[1]]). " + + "Given \(weightShapeArray).") + } + + // inputShape check + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapesCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapesCheck != nil else { + return (false, "Input tensors' shapes are not valid. Check logs for detail.") + } + + // outputshape check + let outputShapes = self.outputTensors!.map { $0.shape } + guard outputShapes.count == outputShapesCheck!.count else { + return (false, "Output tensor's count is not valid. Expect \(outputShapesCheck!.count), given \(outputShapes.count).") + } + for (outputShape, outputShapeCheck) in zip(outputShapes, outputShapesCheck!) { + guard outputShape == outputShapeCheck else { + return (false, "OutputTensor with shape [\(outputShape.description)] is not valid. Expect [\(outputShapeCheck.description)].") + } + } + + return (true, "") + } + + /// Compute sync way. + /// + /// - Parameter computationMode: mode + public func compute(_ computationMode: OperatorComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) aborts calculation cause given invalid data: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + /// Compute async + /// + /// - Parameter computationMode: computationMode + public func computeAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + fatalError() + } + + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + fatalError() + } + + public func updateParams(grads: [Tensor], LR: Float) { + fatalError() + } + + /// Bind according to labels. + /// + /// -Note: if cannot bind all needed parameters. `fatalError` will be raised. + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + let paramsLabels = ["weight"] + + for label in paramsLabels { + let symbol = (symbols.filter {$0.symbolLabel == label}).first + guard symbol != nil else{ + SerranoLogging.errorLogging(message: "\(label) symbol does not exist.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + guard symbol!.symbolType == SymbolType.Tensor else { + SerranoLogging.errorLogging(message: "\(label) symbol should be a tensor symbol.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + let dataSymbol = symbol! as! TensorSymbol + guard dataSymbol.bindedData != nil else { + SerranoLogging.errorLogging(message: "\(label) symbol has no binded data.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Faltal error raised by Serrano. Check log for details.") + } + + self.weight = dataSymbol.bindedData! as! Tensor + + } + } + + /// `Weight` as `TensorSymbol` + /// + /// - Returns: + public func paramSymbols() -> [GraphSymbol] { + let weightTensorSymbol: SerranoTensorSymbol + if self.weight == nil { + // refer from input shape + + // not nil + guard self.inputShape != nil else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) attribute inputShape is nil while its weight tensor is also ni. Need one of them assigned to construct the graph", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Serrano error. Check log.") + } + + // dim check + guard self.inputShape!.rank == 3 else { + SerranoLogging.errorLogging(message: "Attribute inputShape has invalid rank. Expect 3. Given \(self.inputShape!.rank)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError("Serrano error. Check log.") + } + let (channel, _, _) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: self.inputShape!.shapeArray) + let weightShape = TensorShape(dataType: .float, shape: [self.numFilters, channel, self.kernelSize[0], self.kernelSize[1]]) + weightTensorSymbol = SerranoTensorSymbol("weight", dataSource: SymbolDataSource.User, shape: weightShape) + } else { + weightTensorSymbol = SerranoTensorSymbol("weight", dataSource: SymbolDataSource.User, shape: self.weight!.shape) + } + return [weightTensorSymbol as GraphSymbol] + } + + /// Cpu calculation + internal func cpu() { + // call dilation function + if self.dilation[0] > 1 || self.dilation[1] > 1 { + self.cpu_dilation() + } + + regular(OperatorComputationMode.CPU) + } + + /// GPU calcualtion + internal func gpu() { + // call dilation function + if self.dilation[0] > 1 || self.dilation[1] > 1 { + self.gpu_dilation() + } + + regular(OperatorComputationMode.GPU) + } + + /// Use Img2Col to calcualte result. + /// 1. Convert each input tensor via Img2Col to a 2D tensor `A` with shape `[out_Height x out_Width, channel x kernelSize[0] x kernelSize[1]]`; + /// 2. We view weight tensor as a 2D tensor `B` with shape `[num_filter,channel x kernelSize[0] x kernelSize[1]]`; + /// 3. Do matrix multiplication `AxB` with `transposeB` setting as `true`. + /// + /// - Parameter mode: computation mode + internal func regular(_ mode: OperatorComputationMode) { + // temp make weight's shape 2D faking it as a 2D tensor + let originWeightShapeArray = self.weight!.shape.shapeArray + self.weight!.shape = TensorShape(dataType: self.weight!.shape.dataType, + shape: [originWeightShapeArray[0], + originWeightShapeArray[1] * originWeightShapeArray[2] * originWeightShapeArray[3]]) + + let workGroup = DispatchGroup() + for (input, output) in zip(self.inputTensors!, self.outputTensors!) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + // img2col + let img2colOP = Img2ColOperator(patchSize: self.kernelSize, stride: self.stride, + channelPosition: self.channelPosition, + padMode: self.padMode, + inputTensors: [input], + disableInputOutputCheck: true) + let outShape = img2colOP.outputShape(shapeArray: [input.shape])!.first! + let colTensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(outShape) + img2colOP.outputTensors = [colTensor] + img2colOP.compute(mode) + + // fake output tensor shape as a 2D shape + let outputShape = output.shape + output.shape = TensorShape(dataType: outputShape.dataType, + shape: [outputShape.shapeArray[0] * outputShape.shapeArray[1], outputShape.shapeArray[2]]) + + // matrix mult + let matrixMultOp = MatrixMultOperator(transposeB: true, + inputTensors: [colTensor, self.weight!], + outputTensors: [output], + disableInputOutputCheck: true) + matrixMultOp.compute(mode) + + // change back output tensor shape + output.shape = outputShape + + workGroup.leave() + } + } + workGroup.wait() + + // change weight's shape info back + self.weight!.shape = TensorShape(dataType: self.weight!.shape.dataType, shape: originWeightShapeArray) + } + + internal func cpu_dilation() { + //TODO: implementaion + } + + internal func gpu_dilation() { + //TODO: implementaion + } + +} diff --git a/Source/Serrano/operators/nn/convolution/img2col_op.metal b/Source/Serrano/operators/nn/convolution/img2col_op.metal new file mode 100644 index 0000000..b8a285d --- /dev/null +++ b/Source/Serrano/operators/nn/convolution/img2col_op.metal @@ -0,0 +1,105 @@ +// +// img2col.metal +// serrano +// +// Created by ZHONGHAO LIU on 8/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +typedef struct { + short channelPosition; // 0 --> First, 1 --> Last + short paddingMode; // 0 --> Valid, 1 --> Same + float paddingValue; + int channels; + int inputWidth; + int inputHeight; + int kernelScanningXPatchCount; + int strideWidth; + int strideHeight; + int patchWidth; + int patchHeight; +} Img2ColInfo; + +/// Get channel from input +enum ChannelPosition {First, Last}; +ChannelPosition getChannelPosition(int channelPosition); +ChannelPosition getChannelPosition(int channelPosition) { + ChannelPosition pos = First; + if (channelPosition == 1) { + pos = Last; + } + return pos; +} + +/// Get padding mode +enum PaddingMode {Valid, Same}; +PaddingMode getPaddingMode(int paddingMode); +PaddingMode getPaddingMode(int paddingMode) { + PaddingMode mode = Valid; + if (paddingMode == 1) { + mode = Same; + } + return mode; +} + +/// Each thread copy one value +void kernel Img2col (constant float* input [[ buffer(0) ]], + device float* output [[ buffer(1) ]], + constant Img2ColInfo& info [[ buffer(2) ]], + uint3 group_id [[ threadgroup_position_in_grid ]], + uint3 thread_id_group [[ thread_position_in_threadgroup ]]) { + + + int strideWidth = info.strideWidth; + int strideHeight = info.strideHeight; + int channels = info.channels; + int patchWidth = info.patchWidth; + int patchHeight = info.patchHeight; + float paddingValue = info.paddingValue; + int inputWidth = info.inputWidth; + int inputHeight = info.inputHeight; + ChannelPosition channelPosition = getChannelPosition(info.channelPosition); + PaddingMode paddingMode = getPaddingMode(info.paddingMode); + + // get thread group info + int patchChannelIndex = group_id.z; + int patchHightIndex = group_id.y / info.kernelScanningXPatchCount; // patchX in input + int patchWidthIndex = group_id.y % info.kernelScanningXPatchCount; // patchY in input + int patchElementHightIndex = thread_id_group.y; + int patchElementWidthIndex = thread_id_group.x; + + // get input offset + int inputOffset; + if (channelPosition == First) { + int channelCount = inputWidth * inputHeight; // no. of elements in one channel + int channelOffset = channelCount * patchChannelIndex; // start of channel + int patchOffset = patchHightIndex * (inputWidth * strideHeight) + patchWidthIndex * strideWidth; // start of patch + int elementOffset = patchElementHightIndex * inputWidth + patchElementWidthIndex; // locate element in patch + inputOffset = channelOffset + patchOffset + elementOffset; + } else { + int rowCount = channels * inputWidth; // num of element in each row + int channelOffset = patchChannelIndex; + int patchOffset = patchHightIndex * (rowCount * strideHeight) + patchWidthIndex * strideWidth * channels; + int elementOffset = patchElementHightIndex * rowCount + patchElementWidthIndex * channels; + inputOffset = channelOffset + patchOffset + elementOffset; + } + + // get output offset + int patchCount = patchHeight * patchWidth; + int outputOffset = (group_id.y * patchCount * channels + patchChannelIndex * patchCount) + + (patchElementHightIndex * patchWidth + patchElementWidthIndex); + + // assign + int inputElementX = patchHightIndex * strideHeight + patchElementHightIndex; + int inputElementY = patchWidthIndex * strideWidth + patchElementWidthIndex; + if (inputElementX >= inputHeight || inputElementY >= inputWidth) { // boundary check + if (paddingMode == Same) { // only same use padding value + output[outputOffset] = paddingValue; + } + } else { + output[outputOffset] = input[inputOffset]; + } +} diff --git a/Source/Serrano/operators/nn/convolution/img2col_op.swift b/Source/Serrano/operators/nn/convolution/img2col_op.swift new file mode 100644 index 0000000..c4dd85f --- /dev/null +++ b/Source/Serrano/operators/nn/convolution/img2col_op.swift @@ -0,0 +1,491 @@ +// +// img2col.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + +/// Corresponding struct `Img2ColInfo` in img2col_op.metal +public struct Img2ColInfo { + /// 0 --> First, 1 --> Last + var channelPosition: MetalShort + + /// 0 --> Valid, 1 --> Same + var paddingMode: MetalShort + + /// padding value + var paddingValue: MetalFloat + + /// number of channels + var channels: MetalInt + + /// input width + var inputWidth: MetalInt + + /// input height + var inputHeight: MetalInt + + /// kernel scanning patch count in X direction + var kernelScanningXPatchCount: MetalInt + + /// stride width + var strideWidth: MetalInt + + /// stride height + var strideHeight: MetalInt + + /// patch width + var patchWdith: MetalInt + + /// patch height + var patchHeight: MetalInt +} + +/** +Operator works like img2col in matlab. +It converts any 3D tensor (`[H, W, C]` or `[C, H, W]`) into a 2D tensor (`[H*W, C*M*N]`) according to patchSize `[M, N]` and stride. +*/ +public class Img2ColOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String = "Img2col" + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = true + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + /// The patch size. 2D vecotr. `[patchHeight, patchWidth]` + public var patchSize: [Int] + + /// The stride. 2D vector. Default is `[1, 1]`. `[strideHeight, strideWidth]` + public var stride: [Int] = [1, 1] + + /// Channel position. Default is `ImageChannelOrder.First` + public var channelPosition: TensorChannelOrder = .First + + /// Padding mode. Default is `PaddingMode.Valid` + public var padMode: PaddingMode = .Valid + + /// Padding value + public var paddingValue: Float = 0.0 + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Init + + /// Designated init + /// + /// - Parameters: + /// - patchSize: [patchHeight, patchWidth] + /// - channelPosition: channelPosition description + /// - padMode: padMode + /// - stride: [strideHeight, strideWidth] + /// - computationDelegate: computationDelegate description + /// - inputTensors: inputTensors description + /// - outputTensors: outputTensors description + /// - operatorLabel: operatorLabel description + public init(patchSize: [Int], stride: [Int], + channelPosition: TensorChannelOrder = .First, + padMode: PaddingMode = PaddingMode.Valid, + computationDelegate: OperatorCalculationDelegate? = nil, + inputTensors: [Tensor]? = nil, outputTensors: [Tensor]? = nil, + operatorLabel: String = "Img2ColOp", + disableInputOutputCheck: Bool = false) { + self.patchSize = patchSize + self.channelPosition = channelPosition + self.padMode = padMode + self.stride = stride + self.computationDelegate = computationDelegate + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.operatorLabel = operatorLabel + self.disableInputOutputCheck = disableInputOutputCheck + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Compute output shape according `numFilters`, `kernelSize`, `stride` and `dilation`. + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // patch size validation check + guard self.patchSize.count == 2 && self.patchSize[0] > 0 && self.patchSize[1] > 0 else { + SerranoLogging.errorLogging(message: "Invalid patchSize \(self.patchSize).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // stride check + guard self.stride.count == 2 && self.stride[0] > 0 && self.stride[1] > 0 else { + SerranoLogging.errorLogging(message: "Invalid stride \(self.stride).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + var outShapes = [TensorShape]() + for inShape in shapes { + let shapeArray = inShape.shapeArray + guard inShape.rank == 3 && shapeArray[0] > 0 && shapeArray[1] > 0 && shapeArray[2] > 0 else { + SerranoLogging.errorLogging(message: "Invalid input shape \(inShape.description)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // get input shape values + let (channel, height, width) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: shapeArray) + + // compute output shape array + var outWidth = 0 + var outHeight = 0 + if self.padMode == .Valid { + // In valid mode, if the input size is less than patchSize. We do not accept it + guard height >= self.patchSize[0] && width >= self.patchSize[1] else { + SerranoLogging.errorLogging(message: "Padding mode is Valid and the input shape \(inShape.description) with width \(width) and height \(height) is not valid with patchSize \(self.patchSize).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + } + outHeight = kernelScanningOutSize(self.padMode, inputSize: height, kernelSize: self.patchSize[0], stride: self.stride[0]) + outWidth = kernelScanningOutSize(self.padMode, inputSize: width, kernelSize: self.patchSize[1], stride: self.stride[1]) + // valid out shape + guard outHeight > 0 && outWidth > 0 else { + SerranoLogging.errorLogging(message: "Input shape \(inShape.description) is not valid which will lead to negative output dimension.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + outShapes.append(TensorShape(dataType: inShape.dataType, shape: [outHeight * outWidth, + self.patchSize[0] * self.patchSize[1] * channel])) + + } + return outShapes + } + + + /// Check input and output tensors. + /// + /// - Returns: return value description + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input not nil + guard self.inputTensors != nil else { + return (false, "Attribute inputTensors is nil") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Attribute outputTensors is nil") + } + + // input shape check + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapeCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapeCheck != nil else { + return (false, "Input tensors are not valid. Check log for details.") + } + + // output shape check + let outputShapes = self.outputTensors!.map { $0.shape } + guard outputShapes.count == outputShapeCheck!.count else { + return (false, "Attribute outputTensors should have same number of tensors as inputTensors. " + + "Expect \(self.inputTensors!.count) tensors, given \(self.outputTensors!.count) tensors.") + } + for (outputShape, checkShape) in zip(outputShapes, outputShapeCheck!) { + guard outputShape == checkShape else { + return (false, "One of outputTensors has invalid shape. Expect shape \(checkShape.description), given \(outputShape.description)") + } + } + + return (true, "") + } + + /// Compute sync way. + /// + /// - Parameter computationMode: mode + public func compute(_ computationMode: OperatorComputationMode) { + // check + if !self.disableInputOutputCheck { + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) aborts calculation cause given invalid data: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + /// Compute async + /// + /// - Parameter computationMode: computationMode + public func computeAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + + /// This operator itself does not do any grad update. + /// + /// - Parameters: + /// - computationMode: computationMode description + /// - Returns: return value description + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + return [:] + } + + + /// This operator itself does not do any grad update. + /// + /// - Parameters: + /// - computationMode: computationMode description + /// - upGrads: upGrads description + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.computationDelegate?.operatorWillBeginGradsComputation(self) + let result = self.gradCompute(computationMode) + self.computationDelegate?.operatorDidEndGradsComputation(self, grads: result) + } + } + + + /// Do nothing. No param to update + /// + /// - Parameters: + /// - grads: grads description + /// - LR: LR description + public func updateParams(grads: [Tensor], LR: Float) { + // no param to update + return + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator returns no param symbols + /// + /// - Returns: empty array + public func paramSymbols() -> [GraphSymbol] { + return [] + } + + /// CPU calculation + internal func cpu() { + let patchHeight = self.patchSize[0] + let patchWidth = self.patchSize[1] + let patchElementCount = patchWidth * patchHeight + + let strideHeight = self.stride[0] + let strideWidth = self.stride[1] + + let workGroup = DispatchGroup() + for (inTensor, outTensor) in zip(self.inputTensors!, self.outputTensors!) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + let inShapeArray = inTensor.shape.shapeArray + let (channels, inHeight, inWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: inShapeArray) + + // get out dim size + let outHeight = kernelScanningOutSize(self.padMode, inputSize: inHeight, kernelSize: patchHeight, stride: strideHeight) + let outWidth = kernelScanningOutSize(self.padMode, inputSize: inWidth, kernelSize: patchWidth, stride: strideWidth) + + let patchCalculationGroup = DispatchGroup() + for i in 0.. Img2ColInfo { + let (channel, inputHeight, inputWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: inputShape.shapeArray) + let kernelScanningXPatchCount = kernelScanningOutSize(self.padMode, inputSize: inputWidth, + kernelSize: self.patchSize[1], stride: self.stride[1]) + return Img2ColInfo(channelPosition: MetalShort(self.channelPosition.rawValue), + paddingMode: MetalShort(self.padMode.rawValue), paddingValue: self.paddingValue, + channels: MetalInt(channel), inputWidth: MetalInt(inputWidth), inputHeight: MetalInt(inputHeight), + kernelScanningXPatchCount: MetalInt(kernelScanningXPatchCount), + strideWidth: MetalInt(self.stride[1]), strideHeight: MetalInt(self.stride[0]), + patchWdith: MetalInt(self.patchSize[1]), patchHeight: MetalInt(self.patchSize[0])) + } + + /// GPU calculation. + /// + /// Split the output 2D matrix into threadgroups based on each patch and channel index. + /// Calculate each element in each group independently. + internal func gpu() { + // prepare resource + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffers: [MTLCommandBuffer] = [MTLCommandBuffer]() + var inputBuffers: [MTLBufferResource] = [MTLBufferResource]() + var resultBuffers: [MTLBufferResource] = [MTLBufferResource]() + + //// kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + //// command buffers + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + for _ in 0...stride, at: 2) + + // View output matrix as a [outputHeight * outputWidth, patchCount, channels] matrix, las dim is channels + // Each group, spawn thread for each point + let outHeight = self.outputTensors![bufferIndex].shape.shapeArray[0] + let channels = Int(info.channels) + let threadsPerThreadgroup = MTLSizeMake(self.patchSize[1], // width + self.patchSize[0], // height + 1) + let threadgroupsPerGrid = MTLSizeMake(1, + outHeight, + channels) + SerranoLogging.stdLogging(message: "Dispatch group configured with threadgroupsPerGrid: \(threadgroupsPerGrid), threadsPerThreadgroup: \(threadsPerThreadgroup) requested by operator \(self.operatorLabel)", + file: "\(#file)", function: "\(#function)", line: "\(#line)", loggingLevel: .LowLevel) + encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) + + encoder.endEncoding() + + buffer.commit() + buffer.waitUntilCompleted() + + resourcePrepareGroup.leave() + } + } + + resourcePrepareGroup.wait() + } + + +} diff --git a/Source/Serrano/operators/nn/misc.swift b/Source/Serrano/operators/nn/misc.swift new file mode 100644 index 0000000..f643900 --- /dev/null +++ b/Source/Serrano/operators/nn/misc.swift @@ -0,0 +1,106 @@ +// +// misc.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/20/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +/** +Padding mode for operators involving with kernel calculation, like pooling and convolution operators. +We follow same behavior as TensorFlow defined. + +## Valid padding +No padding at all when moving kernel on input tensor. + +## Same padding +Padding to cover all elements +*/ +public enum PaddingMode: Int { + case Valid = 0 + case Same = 1 + + var description: String { + get { return String(reflecting: self) } + } +} + + +/// Caculate output size for convolutiona like kernel scanning operation. +/// +/// ## Valid +/// `Int(Float((inputSize - stride + 1) / patchSize).rounded(.up))` +/// +/// ## Same +/// `Int(Float(inputSize / stride).rounded(.up))` +/// +/// - Parameters: +/// - mode: mode +/// - inputSize: inputSize description +/// - kernelSize: kernel size +/// - stride: stride +/// - Returns: return value +public func kernelScanningOutSize(_ mode: PaddingMode, inputSize: Int, kernelSize: Int, stride: Int) -> Int { + switch mode { + case .Same: + let val = Float(inputSize) / Float(stride) + return Int(val.rounded(.up)) + case .Valid: + let val = Float(inputSize - kernelSize + 1) / Float(stride) + return Int(val.rounded(.up)) + } +} + + +/** +The channel order in a N-D tensor. + +## First +Coming before tensor shape. +For example an image with height `H` and width `W`, it will be represented as [C, H, W] + +## Last +Coming after tensor shape. +For example an image with height `H` and width `W`, it will be represented as [C, H, W] + +*/ +public enum TensorChannelOrder: Int { + case First = 0 + case Last = 1 + + var description: String { + get { return String(reflecting: self) } + } +} + + +/// According to `channelOrder`, parse `inputShapeArray` to channel, height and width +/// +/// - Note: rank of `inputShapeArray` should be `3`. +/// +/// - Parameters: +/// - channelOrder: channelOrder +/// - shapeArray: inputShapeArray +/// - Returns: return value +public func parseImgChannelShapeInfo(_ channelOrder: TensorChannelOrder, shapeArray: [Int]) -> (channel:Int, height:Int, width: Int) { + + guard shapeArray.count == 3 else { + SerranoLogging.errorLogging(message: "Input array should contain 3 element. Given \(shapeArray.count) elements", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + if channelOrder == .First { + return (shapeArray[0], shapeArray[1], shapeArray[2]) + } else if channelOrder == .Last { + return (shapeArray[2], shapeArray[0], shapeArray[1]) + } else { + fatalError("Not implemented") + } +} + + + diff --git a/Source/Serrano/operators/nn/pooling/pooling_op.metal b/Source/Serrano/operators/nn/pooling/pooling_op.metal new file mode 100644 index 0000000..6f44e09 --- /dev/null +++ b/Source/Serrano/operators/nn/pooling/pooling_op.metal @@ -0,0 +1,142 @@ +// +// pooling_op.metal +// serrano +// +// Created by ZHONGHAO LIU on 7/20/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include +using namespace metal; + +typedef struct { + short channelPosition; // 0 --> First, 1 --> Last + ushort kernelSizeHeight; + ushort kernelSizeWidth; + ushort strideHeight; + ushort strideWidth; + uint inHeight; + uint inWidth; + uint inChannel; + uint outHeight; + uint outWidth; + uint outChannel; +} Pool2DInfo; + +//TODO: Loop unrolling implementation for some specific kernel sizes: 2x2, 3x3, 5x5 + +void kernel MaxPool2D(constant float* input [[ buffer(0) ]], + device float* output [[ buffer(1) ]], + constant Pool2DInfo& info [[ buffer(2) ]], + uint3 gid [[ thread_position_in_grid ]]) { + // check boundary + if (gid.x >= info.outWidth || gid.y >= info.outHeight) { + return; + } + + // get pooling starting index in input + uint elementStartIndex = gid.y * info.strideHeight * info.inWidth + gid.x * info.strideWidth + gid.z * info.inWidth * info.inHeight; + if (info.channelPosition == 1) { // channel last + elementStartIndex = gid.y * info.strideHeight * info.inWidth * info.inChannel + gid.x * info.strideWidth * info.inChannel + gid.z; + } + + // valid input boundary + int validHeightPoolCount = min(gid.y * info.strideHeight + info.kernelSizeHeight, info.inHeight) - gid.y * info.strideHeight; + int validWidthPoolCount = min(gid.x * info.strideWidth + info.kernelSizeWidth, info.inWidth) - gid.x * info.strideWidth; + + // pooling + float max_val = -INFINITY; + if (info.channelPosition == 0) { // channel first + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + max_val = max(max_val, input[elementStartIndex + i * info.inWidth + j]); + } + } + output[gid.z * info.outHeight * info.outWidth + gid.y * info.outWidth + gid.x] = max_val; + } else { // channel last + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + max_val = max(max_val, input[elementStartIndex + i * info.inWidth * info.inChannel + j * info.inChannel]); + } + } + output[gid.y * info.outWidth * info.outChannel + gid.x * info.outChannel + gid.z] = max_val; + } +} + +void kernel AvgPool2D (constant float* input [[ buffer(0) ]], + device float* output [[ buffer(1) ]], + constant Pool2DInfo& info [[ buffer(2) ]], + uint3 gid [[ thread_position_in_grid ]]) { + // check boundary + if (gid.x >= info.outWidth || gid.y >= info.outHeight) { + return; + } + + // get pooling starting index in input + uint elementStartIndex = gid.y * info.strideHeight * info.inWidth + gid.x * info.strideWidth + gid.z * info.inWidth * info.inHeight; + if (info.channelPosition == 1) { // channel last + elementStartIndex = gid.y * info.strideHeight * info.inWidth * info.inChannel + gid.x * info.strideWidth * info.inChannel + gid.z; + } + + // valid input boundary + int validHeightPoolCount = min(gid.y * info.strideHeight + info.kernelSizeHeight, info.inHeight) - gid.y * info.strideHeight; + int validWidthPoolCount = min(gid.x * info.strideWidth + info.kernelSizeWidth, info.inWidth) - gid.x * info.strideWidth; + + // pooling + float sum = 0.0f; + if (info.channelPosition == 0) { // channel first + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + sum += input[elementStartIndex + i * info.inWidth + j]; + } + } + output[gid.z * info.outHeight * info.outWidth + gid.y * info.outWidth + gid.x] = sum / (info.kernelSizeHeight * info.kernelSizeWidth); + } else { // channel last + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + sum += input[elementStartIndex + i * info.inWidth * info.inChannel + j * info.inChannel]; + } + } + output[gid.y * info.outWidth * info.outChannel + gid.x * info.outChannel + gid.z] = sum / (info.kernelSizeHeight * info.kernelSizeWidth); + } + +} + +void kernel SumPool2D (constant float* input [[ buffer(0) ]], + device float* output [[ buffer(1) ]], + constant Pool2DInfo& info [[ buffer(2) ]], + uint3 gid [[ thread_position_in_grid ]]) { + // check boundary + if (gid.x >= info.outWidth || gid.y >= info.outHeight) { + return; + } + + // get pooling starting index in input + uint elementStartIndex = gid.y * info.strideHeight * info.inWidth + gid.x * info.strideWidth + gid.z * info.inWidth * info.inHeight; + if (info.channelPosition == 1) { // channel last + elementStartIndex = gid.y * info.strideHeight * info.inWidth * info.inChannel + gid.x * info.strideWidth * info.inChannel + gid.z; + } + + // valid input boundary + int validHeightPoolCount = min(gid.y * info.strideHeight + info.kernelSizeHeight, info.inHeight) - gid.y * info.strideHeight; + int validWidthPoolCount = min(gid.x * info.strideWidth + info.kernelSizeWidth, info.inWidth) - gid.x * info.strideWidth; + + // pooling + float sum = 0.0f; + if (info.channelPosition == 0) { // channel first + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + sum += input[elementStartIndex + i * info.inWidth + j]; + } + } + output[gid.z * info.outHeight * info.outWidth + gid.y * info.outWidth + gid.x] = sum; + } else { // channel last + for (int i = 0; i < validHeightPoolCount; i++) { + for (int j = 0; j < validWidthPoolCount; j++) { + sum += input[elementStartIndex + i * info.inWidth * info.inChannel + j * info.inChannel]; + } + } + output[gid.y * info.outWidth * info.outChannel + gid.x * info.outChannel + gid.z] = sum; + } + +} diff --git a/Source/Serrano/operators/nn/pooling/pooling_op.swift b/Source/Serrano/operators/nn/pooling/pooling_op.swift new file mode 100644 index 0000000..cff2057 --- /dev/null +++ b/Source/Serrano/operators/nn/pooling/pooling_op.swift @@ -0,0 +1,698 @@ +// +// pooling_op.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/20/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Dispatch +import Accelerate + + +/// Mirror struct of `Pool2DInfo` in `pooling_op.metal` +public struct Pool2DInfo { + var channelPosition: MetalShort + var kernelSizeHeight: MetalUShort + var kernelSizeWidth: MetalUShort + var strideHeight: MetalUShort + var strideWidth: MetalUShort + var inHeight: MetalUInt + var inWidth: MetalUInt + var inChannel: MetalUInt + var outHeight: MetalUInt + var outWidth: MetalUInt + var outChannel: MetalUInt + + /// Generate `Pool2DInfo` from a `Pooling2DOperator`'s information. + /// + /// - Parameters: + /// - op: operator + /// - inputSize: input size + /// - outputSize: output size + /// - Returns: `Pool2DInfo` + static func makePool2DInfo(op: Pooling2DOperator, inputSize: [Int], outputSize: [Int]) -> Pool2DInfo { + let (inChannel, inHeight, inWidth) = parseImgChannelShapeInfo(op.channelPosition, shapeArray: inputSize) + let (outChannel, outHeight, outWidth) = parseImgChannelShapeInfo(op.channelPosition, shapeArray: outputSize) + return Pool2DInfo(channelPosition: MetalShort(op.channelPosition.rawValue), + kernelSizeHeight: MetalUShort(op.kernelSize[0]), kernelSizeWidth: MetalUShort(op.kernelSize[1]), + strideHeight: MetalUShort(op.stride[0]), strideWidth: MetalUShort(op.stride[1]), + inHeight: MetalUInt(inHeight), inWidth: MetalUInt(inWidth), inChannel: MetalUInt(inChannel), + outHeight: MetalUInt(outHeight), outWidth: MetalUInt(outWidth), outChannel: MetalUInt(outChannel)) + } +} + +/** +This class is an abstract class for 2D pooling operators. +*/ +public class Pooling2DOperator: ComputableOperator { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Attributes + + /// Operator label. Conforms to `ComputableOperator` + public var operatorLabel: String + + /// This operator does not operator on GPU. Conforms to `ComputableOperator` + public var metalKernelFuncLabel:String = "" // need override by child class + + /// Conforms to `ComputableOperator` + public var computationDelegate: OperatorCalculationDelegate? + + /// Conforms to `ComputableOperator` + public var inputTensors: [Tensor]? + + /// Conforms to `ComputableOperator` + public var outputTensors: [Tensor]? + + /// Pad mode, default is `PaddingMode.Valid`. + public var paddingMode: PaddingMode = PaddingMode.Valid + + /// Kernel size array which contains the kernel size in each dimension. + public var kernelSize: [Int] + + /// Stride array which contains the stride in each dimension. + public var stride: [Int] + + /// CPU computation block. + /// Two tensors are input and output tensors. + public lazy var cpuComputeBlock: ((Tensor, Tensor) -> Void)? = nil + + /// Channel position. Default is `ImageChannelOrder.First` + public var channelPosition: TensorChannelOrder = .First + + /// If `true`, operator will not call `inputOutputTensorsCheck()` before doing calculation. + /// This is used inside framework to speed up in situation we know it will not be wrong. + public var disableInputOutputCheck: Bool = false + + /// Indicate if this operator would do paramter update. + public var trainable: Bool = false + + /// The mapping type of this operator. + /// `OneToOne` for this operator. + public var mapType: OperatorMappingType { + get { + return OperatorMappingType.OneToOne + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Initializers + + /// Initializer. + /// + /// - Parameters: + /// - kernelSize: Array of int values. Should has 2 elemetns for height and width dimesnsions. + /// - stride: Array of int values. If `stride` is `nil`, it will be assigned as same value as `kernelSize` + /// - channelPosition: channel position in input data + /// - paddingMode: paddingMode + /// - inputTensors: inputTensors + /// - outputTensors: outputTensors + /// - computationDelegate: computationDelegate + /// - operatorLabel: operatorLabel + required public init(kernelSize: [Int], + stride: [Int]? = nil, + channelPosition: TensorChannelOrder = TensorChannelOrder.First, + paddingMode: PaddingMode = PaddingMode.Valid, + inputTensors: [Tensor]? = nil, + outputTensors: [Tensor]? = nil, + computationDelegate: OperatorCalculationDelegate? = nil, + operatorLabel: String = "Pooling") { + self.kernelSize = kernelSize + if stride == nil { + self.stride = kernelSize + } else { + self.stride = stride! + } + self.channelPosition = channelPosition + self.paddingMode = paddingMode + self.inputTensors = inputTensors + self.outputTensors = outputTensors + self.computationDelegate = computationDelegate + self.operatorLabel = operatorLabel + self.kernelSize = kernelSize + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods + + /// Compute output shape according to `kernelSize`, `stride` and `paddingMode` + /// + /// - Parameter shapes: shapes description + /// - Returns: return value description + public func outputShape(shapeArray shapes: [TensorShape]) -> [TensorShape]? { + // input not empty + guard shapes.count != 0 else { + SerranoLogging.errorLogging(message: "Input shapes array is empty", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // kernel valid + guard self.kernelSize.count == 2 && self.kernelSize[0] > 0 && self.kernelSize[1] > 0 else { + SerranoLogging.errorLogging(message: "Invalid kernelSize: \(self.kernelSize)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + // stride check + guard self.stride.count == 2 && self.stride[0] > 0 && self.stride[0] > 0 else { + SerranoLogging.errorLogging(message: "Invalid stride: \(self.stride)", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + var outputShapes = [TensorShape]() + for shape in shapes { + // check valid shape + guard shape.rank == 3 && shape.shapeArray[0] > 0 && shape.shapeArray[1] > 0 && shape.shapeArray[2] > 0 else { + SerranoLogging.errorLogging(message: "Input shape is not valid \(shape.description).", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + let (channel, height, width) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: shape.shapeArray) + var outShapeArray = [kernelScanningOutSize(self.paddingMode, inputSize: height, + kernelSize: self.kernelSize[0], stride: self.stride[0]), + kernelScanningOutSize(self.paddingMode, inputSize: width, + kernelSize: self.kernelSize[1], stride: self.stride[1]), + channel] + if self.channelPosition == TensorChannelOrder.First { + outShapeArray = [channel, + kernelScanningOutSize(self.paddingMode, inputSize: height, + kernelSize: self.kernelSize[0], stride: self.stride[0]), + kernelScanningOutSize(self.paddingMode, inputSize: width, + kernelSize: self.kernelSize[1], stride: self.stride[1])] + } + + // valid out shape + guard outShapeArray[0] > 0 && outShapeArray[1] > 0 && outShapeArray[2] > 0 else { + SerranoLogging.errorLogging(message: "Input shape \(shape.description) is not valid which will lead to negative output dimension.", + file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + outputShapes.append(TensorShape(dataType: shape.dataType, shape: outShapeArray)) + } + + return outputShapes + } + + /// Check validation between `inputTensors`/`outputTensors` and `stride`, `kernelSize`. + /// + /// - Returns: check indicating if pass checking, msg for error message. + public func inputOutputTensorsCheck() -> (check: Bool, msg: String) { + // input not nil + guard self.inputTensors != nil else { + return (false, "Attribute inputTensors is nil") + } + + // output not nil + guard self.outputTensors != nil else { + return (false, "Attribute outputTensors is nil") + } + + // input shape check + let inputShapes = self.inputTensors!.map { $0.shape } + let outputShapeCheck = self.outputShape(shapeArray: inputShapes) + guard outputShapeCheck != nil else { + return (false, "Input tensors are not valid. Check log for details.") + } + + // output shape check + let outputShapes = self.outputTensors!.map { $0.shape } + guard outputShapes.count == outputShapeCheck!.count else { + return (false, "Attribute outputTensors should have same number of tensors as inputTensors. " + + "Expect \(self.inputTensors!.count) tensors, given \(self.outputTensors!.count) tensors.") + } + for (outputShape, checkShape) in zip(outputShapes, outputShapeCheck!) { + guard outputShape == checkShape else { + return (false, "One of outputTensors has invalid shape. Expect shape \(checkShape.description), given \(outputShape.description)") + } + } + + return (true, "") + } + + + /// Compute sync + /// + /// - Parameter computationMode: computationMode + public func compute(_ computationMode: OperatorComputationMode) { + // check + let (pass, msg) = self.inputOutputTensorsCheck() + guard pass else { + SerranoLogging.errorLogging(message: "Operator \(self.operatorLabel) aborts calculation cause given invalid data: \(msg)", file: "\(#file)", function: "\(#function)", line: "\(#line)") + fatalError() + } + + self.computationDelegate?.operatorWillBeginComputation(self) + + switch computationMode { + case .GPU: + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + SerranoLogging.warningLogging(message: "Serrano Engine has no available configured GPU device. Use CPU doing calculation instead.", file: "\(#file)", function: "\(#function)", line: "\(#line)") + self.cpu() + } else { + self.gpu() + } + case .CPU: + self.cpu() + case .Auto: + // TODO: More intelligent way to decide + if self.inputTensors![0].count > 1000000 && SerranoEngine.configuredEngine.hasAvailableGPU(){ + self.gpu() + } else { + self.cpu() + } + } + self.computationDelegate?.operatorDidEndComputation(self, outputTensors: self.outputTensors!) + } + + + /// Compute async + /// + /// - Parameter computationMode: computationMode + public func computeAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + self.compute(computationMode) + } + } + + /// Calulate grads sync. + /// All unary operator return grads tensor with same number and shape as attribute `inputTensors`. + /// + /// - Parameters: + /// - computationMode: computationMode + /// - Returns: return grads tensor + public func gradCompute(_ computationMode: OperatorComputationMode) -> [String: DataSymbolSupportedDataType] { + //TODO: Implementation + fatalError("Not implemented") + } + + /// Cal grads async + /// + /// - Parameters: + /// - computationMode: computationMode + public func gradComputAsync(_ computationMode: OperatorComputationMode) { + // check delegate + OperatorUtils.delegateNilWarning(op: self, file: "\(#file)", function: "\(#function)", line: #line) + + DispatchQueue.global(qos: .userInitiated).async { + _ = self.gradCompute(computationMode) + } + } + + /// This operator has no parameters. Do nothing + /// + public func bindParamSymbols(_ symbols: [GraphSymbol]) { + + } + + /// This operator has no parameters. + /// + /// - Returns: An empty array + public func paramSymbols() -> [GraphSymbol] { + return [GraphSymbol]() + } + + /// CPU calcualtion. Call `cpuComputeBlock` which is defined in subclass + internal func cpu() { + let workGroup = DispatchGroup() + for (input, output) in zip(self.inputTensors!, self.outputTensors!) { + workGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + self.cpuComputeBlock!(input, output) + workGroup.leave() + } + } + workGroup.wait() + } + + /// GPU calculation + internal func gpu() { + // prepare resources + let resourcePrepareGroup = DispatchGroup() + let engine = SerranoEngine.configuredEngine + var kernel: MTLComputePipelineState? + var commandBuffers: [MTLCommandBuffer] = [MTLCommandBuffer]() + var inputBuffers: [MTLBufferResource] = [MTLBufferResource]() + var outputBuffers: [MTLBufferResource] = [MTLBufferResource]() + var infoBuffers: [MTLBuffer] = [MTLBuffer]() + + // kernel + resourcePrepareGroup.enter() + DispatchQueue.global(qos: .userInitiated).async { + var info = "" + (kernel, info) = engine.loadGPUKernel(kernelLabel: self.metalKernelFuncLabel) + guard kernel != nil else { + fatalError("[Serrano] Failed to load kernel \(self.metalKernelFuncLabel). Info: \(info)") + } + resourcePrepareGroup.leave() + } + + // command buffer + for _ in 0...stride, + options: MTLResourceOptions.storageModeShared) + infoBuffers.append(buffer) + resourcePrepareGroup.leave() + } + } + + resourcePrepareGroup.wait() + + // encoder + for index in 0.. Void in + let workGroup = DispatchGroup() + let outShapeArray = output.shape.shapeArray + let inShapeArray = input.shape.shapeArray + let stride = self.stride + let kernelSize = self.kernelSize + let (channel, outHeight, outWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: outShapeArray) + let (_, inHeight, inWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: inShapeArray) + + // according to channel order, do pooling. + // Here we are trying to taking advantage of spatial locality for input + // TODO: I doubt if this really improve performance. Should do profiling and verify. + if self.channelPosition == TensorChannelOrder.First { + for c in 0.. Void in + let workGroup = DispatchGroup() + let outShapeArray = output.shape.shapeArray + let inShapeArray = input.shape.shapeArray + let stride = self.stride + let kernelSize = self.kernelSize + let (channel, outHeight, outWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: outShapeArray) + let (_, inHeight, inWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: inShapeArray) + + // according to channel order, do pooling. + // Here we are trying to taking advantage of spatial locality for input + // TODO: I doubt if this really improve performance. Should do profiling and verify. + if self.channelPosition == TensorChannelOrder.First { + for c in 0.. Void in + let workGroup = DispatchGroup() + let outShapeArray = output.shape.shapeArray + let inShapeArray = input.shape.shapeArray + let stride = self.stride + let kernelSize = self.kernelSize + let (channel, outHeight, outWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: outShapeArray) + let (_, inHeight, inWidth) = parseImgChannelShapeInfo(self.channelPosition, shapeArray: inShapeArray) + + // according to channel order, do pooling. + // Here we are trying to taking advantage of spatial locality for input + // TODO: I doubt if this really improve performance. Should do profiling and verify. + if self.channelPosition == TensorChannelOrder.First { + for c in 0..= 0`. + public var learningRate: Float + + /// Initial leanring rate + public var initLearningRate: Float { + get { + return self.initialLR + } + } + + /// LR Decay method + public var decayMethod: LearningRateDecayMethod + + /// Learning rate decay per epoch. + /// Before each epoch's parmaeter updating, do `learningRate -= learningRate * decay`. + /// Should `>= 0`. + public var decay: Float + + /// Momentum. + /// Should `>= 0`. + public var momentum: Float + + /// Whether to turn on Nesterov momentum. + /// Default is `false`. + public var nesterov: Bool + + /// Initial leanring rate + internal var initialLR: Float + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Init + + public init(learningRate: Float = 0.001, momentum: Float = 0.0, + decayMethod: LearningRateDecayMethod = LearningRateDecayMethod.Step, decay: Float = 0.0, + nesterov: Bool = false) { + self.initialLR = learningRate + self.learningRate = learningRate + self.momentum = momentum + self.decayMethod = decayMethod + self.decay = decay + self.nesterov = false + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // MARK: - Methods conforming Optimizer protocol + + /// Do some preparing work before each epoch training + /// + /// - Parameter graph: target graph + public func prepare(_ graph: Graph) { + // decay learning rate + self.learningRate = self.decayMethod.decayLR(initialLR: self.initialLR, decay: self.decay, epoch: graph.epoch) + } + + /// Update a data symbol's updated value + /// + //// - Parameters: + /// - dataSymbol: target symbol + /// - gradValue: gradvalue fot this time updating + public func updateParameter(_ dataSymbol: DataSymbol, gradValue: DataSymbolSupportedDataType) { + // momentum + if dataSymbol.symbolType == SymbolType.Scalar { + var scalarSymbol = dataSymbol as! ScalarSymbol + let value: Float = scalarSymbol.bindedData! as! Float + let v: Float = self.momentum * scalarSymbol.currentGrad!.scarlarValue - self.learningRate * gradValue.scarlarValue + if self.nesterov { + scalarSymbol.bindedData! = value + self.momentum * v - self.learningRate * gradValue.scarlarValue + } else { + scalarSymbol.bindedData! = value + v + } + } else { + var tensorSymbol = dataSymbol as! TensorSymbol + let grad = tensorSymbol.currentGrad as! Tensor + let v: Tensor = self.momentum * grad &- self.learningRate * gradValue.tensorValue + if self.nesterov { + // self.learningRate * gradValue.tensorValue cannot use inplace operation. will effect passed-in argument + tensorSymbol.bindedData!.tensorValue &+ (self.momentum &* v) &- self.learningRate * gradValue.tensorValue + } else { + tensorSymbol.bindedData!.tensorValue &+ v + } + print("\(tensorSymbol.symbolLabel)",grad.flatArrayFloat()) + } + } + + +} diff --git a/Source/Serrano/utils/logging.swift b/Source/Serrano/utils/logging.swift new file mode 100644 index 0000000..7320427 --- /dev/null +++ b/Source/Serrano/utils/logging.swift @@ -0,0 +1,59 @@ +// +// logging.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/17/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation + + +public enum SerranoLoggingType{ + case Regular, + MediumLevel, + LowLevel +} + + +public class SerranoLogging { + + public static var enableWarnning = true + + /// If `release` is `true`, all std and warning logging will be omiited. + /// Default is `false`. + /// + public static var release = false + + public static func stdLogging(message: String, file: String, function: String, line: String, loggingLevel: SerranoLoggingType) { + if release { return } + + let fileName = file.components(separatedBy: "/").last + if fileName != nil { + NSLog("[Serrano](File: %@, Function: %@, Line: %@) ==> %@", fileName!.description, function, line, message) + } else { + NSLog("[Serrano](File: %@, Function: %@, Line: %@) ==> %@", file, function, line, message) + } + } + + public static func warningLogging(message: String, file: String, function: String, line: String) { + if release { return } + + let fileName = file.components(separatedBy: "/").last + if fileName != nil { + NSLog("[Serrano, Warning⚠️](File: %@, Function: %@, Line: %@) ==> %@", fileName!.description, function, line, message) + } else { + NSLog("[Serrano, Warning⚠️](File: %@, Function: %@, Line: %@) ==> %@", file, function, line, message) + } + } + + public static func errorLogging(message: String, file: String, function: String, line: String) { + let fileName = file.components(separatedBy: "/").last + if fileName != nil { + NSLog("[Serrano, ERROR‼️](File: %@, Function: %@, Line: %@) ==> %@", fileName!.description, function, line, message) + } else { + NSLog("[Serrano, ERROR‼️](File: %@, Function: %@, Line: %@) ==> %@", file, function, line, message) + } + } +} + diff --git a/Source/Serrano/utils/metal_hardwares.swift b/Source/Serrano/utils/metal_hardwares.swift new file mode 100644 index 0000000..f87538c --- /dev/null +++ b/Source/Serrano/utils/metal_hardwares.swift @@ -0,0 +1,79 @@ +// +// metal_hardwares.swift +// serrano +// +// Created by ZHONGHAO LIU on 4/22/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal + +/// GPU familly not support MetalPerformanceShader +/// Ref: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf +#if os(iOS) +public let GPU_FAMILTY_NOT_SUPPORT_MPS = [ + MTLFeatureSet.iOS_GPUFamily1_v3, +] +#endif + +/** + This util class contains APIs that check the device's hardware capabilities and limits accroding to [Apple's official docs](https://developer.apple.com/metal/availability). + + - Note: The limits values of each type of hardware device should be checked and updated with Apple's [doc](https://developer.apple.com/metal/limits/) frequently. If you find any mismatching with Apple's official doc, please make a PR at github. + */ +public class MetalHardwareChecker { + /// Maximum length of a data block for a function, per render or compute command encode + public static let MAX_BYTES_OF_DATA_BLOCK_PER_KERNEL = 4000 + + // Maximum buffer length + public static let MAX_BUFFER_SIZE_IN_BYTES = Int(2.56e+8) + + + + /// Check if tesor's data size largser thant MAX_BUFFER_SIZE_IN_BYTES + /// + /// - Parameter tensor: tensor + /// + /// - Returns: + /// - result: If fitting + /// - info: Checking infomation + public static func tensorSizeFitCheck(tensor: Tensor) -> (result: Bool, info: String) { + if tensor.allocatedBytes >= MetalHardwareChecker.MAX_BUFFER_SIZE_IN_BYTES { + return (false, "Trying to allocatea MTLBuffer with \(tensor.allocatedBytes) bytes. Metal could not create a MTLBuffer larger than \(MetalHardwareChecker.MAX_BUFFER_SIZE_IN_BYTES) bytes.") + } else { + return (true, "") + } + } + + + /// If current GPU support MetalPerformanceShaser + /// + /// - Returns: result bool + public static func supportMPS() -> Bool { + // macos test + #if os(OSX) + if #available(OSX 10.13, *) { + return true + } else { + return false + } + #endif + + // ios Test + #if os(iOS) + let device = SerranoEngine.configuredEngine.GPUDevice + guard device != nil else { + return false + } + + for feature in GPU_FAMILTY_NOT_SUPPORT_MPS { + if device!.supportsFeatureSet(feature) { + return false + } + } + + return true + #endif + } +} diff --git a/Source/Serrano/utils/type_ext.swift b/Source/Serrano/utils/type_ext.swift new file mode 100644 index 0000000..584bdda --- /dev/null +++ b/Source/Serrano/utils/type_ext.swift @@ -0,0 +1,165 @@ +// +// type_ext.swift +// serrano +// +// Created by ZHONGHAO LIU on 4/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// + +extension Int { + + /// Description + /// + /// - Parameter size: size description + /// - Returns: return value description + func padded(alignmentSize size: Int) -> Int? { + guard self >= 0 && size > 0 else { + SerranoLogging.warningLogging(message: "Undefined padding action from \(self) to \(size).", file: "\(#file)", function: "\(#function)", line: "\(#line)") + return nil + } + + let remainder = self % size + if remainder == 0 { + return self + } else { + return self + size - remainder + } + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +public protocol SupportedScalarDataType { + + var floatValue: Float {get} + +} + +extension Int: SupportedScalarDataType { + + + public var floatValue: Float { + return Float(self) + } + +} + +extension Double: SupportedScalarDataType { + + public var floatValue: Float { + return Float(self) + } +} + +extension Float: SupportedScalarDataType { + + + public var floatValue: Float { + return self + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +public protocol SupportedNestedType {} +extension Array: SupportedNestedType {} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// In Metal, UInt is 32 bits. In iOS, it is 64 bits on 64 bit system. +typealias MetalUInt = UInt32 + +typealias MetalUShort = UInt16 + +typealias MetalShort = Int16 + +typealias MetalInt = Int32 + +typealias MetalFloat = Float32 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +public protocol DataSymbolSupportedDataType { + var description: String {get} + var tensorValue: Tensor {get} + var scarlarValue: Float {get} +} +extension Int: DataSymbolSupportedDataType { + public var description: String { + get { + return "Int value: \(self)" + } + } + + public var tensorValue: Tensor { + get { + fatalError("This DataSymbolSupportedDataType is a scarlar.") + } + } + + public var scarlarValue: Float { + get { + return Float(self) + } + } +} +extension Double: DataSymbolSupportedDataType { + public var description:String { + get { + return "Double value: \(self)" + } + } + + public var tensorValue: Tensor { + get { + fatalError("This DataSymbolSupportedDataType is a scarlar.") + } + } + + public var scarlarValue: Float { + get { + return Float(self) + } + } +} +extension Float: DataSymbolSupportedDataType { + public var description:String { + get { + return "Float value: \(self)" + } + } + + public var tensorValue: Tensor { + get { + fatalError("This DataSymbolSupportedDataType is a scarlar.") + } + } + + public var scarlarValue: Float { + get { + return self + } + } +} +extension Tensor: DataSymbolSupportedDataType { + public var scarlarValue: Float { + get { + fatalError("This DataSymbolSupportedDataType is a tensor.") + } + } + + public var tensorValue: Tensor { + get { + return self + } + } +} + + diff --git a/Source/Serrano/utils/utils.swift b/Source/Serrano/utils/utils.swift new file mode 100644 index 0000000..c8c7bee --- /dev/null +++ b/Source/Serrano/utils/utils.swift @@ -0,0 +1,40 @@ +// +// utils.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/9/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import Metal +import Accelerate + + + +/** + Get `CBLAS_TRANSPOSE` enum value from Bool marker + */ +public func cblasTrans(_ mark: Bool) -> CBLAS_TRANSPOSE { + if mark { + return CblasTrans + } + else { + return CblasNoTrans + } +} + + +/** +Weak reference object. Used in container of weak reference +*/ +public class WeakRef { + weak var value : T? + init (value: T) { + self.value = value + } +} + + + + diff --git a/Source/SupportingFiles/Info.plist b/Source/SupportingFiles/Info.plist new file mode 100644 index 0000000..fada70e --- /dev/null +++ b/Source/SupportingFiles/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Serrano + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Source/SupportingFiles/Serrano.h b/Source/SupportingFiles/Serrano.h new file mode 100644 index 0000000..fd213ea --- /dev/null +++ b/Source/SupportingFiles/Serrano.h @@ -0,0 +1,19 @@ +// +// Serrano.h +// Serrano +// +// Created by ZHONGHAO LIU on 11/1/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#import + +//! Project version number for Serrano. +FOUNDATION_EXPORT double SerranoVersionNumber; + +//! Project version string for Serrano. +FOUNDATION_EXPORT const unsigned char SerranoVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Source/library/FBSUtil/FBSUtil.c b/Source/library/FBSUtil/FBSUtil.c new file mode 100644 index 0000000..e116432 --- /dev/null +++ b/Source/library/FBSUtil/FBSUtil.c @@ -0,0 +1,13 @@ +// +// FBSUtil.c +// serrano +// +// Created by ZHONGHAO LIU on 7/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#include "FBSUtil.h" + +void hello() { + printf("\n\nHello world\n\n"); +} diff --git a/Source/library/FBSUtil/FBSUtil.h b/Source/library/FBSUtil/FBSUtil.h new file mode 100644 index 0000000..e1c8aeb --- /dev/null +++ b/Source/library/FBSUtil/FBSUtil.h @@ -0,0 +1,16 @@ +// +// FBSUtil.h +// serrano +// +// Created by ZHONGHAO LIU on 7/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +#ifndef FBSUtil_h +#define FBSUtil_h + +#include + +void hello(); + +#endif /* FBSUtil_h */ diff --git a/Source/library/FBSUtil/README.md b/Source/library/FBSUtil/README.md new file mode 100644 index 0000000..fe0ddb1 --- /dev/null +++ b/Source/library/FBSUtil/README.md @@ -0,0 +1,4 @@ +# C Library +This folder contains C modules used in Serrano. + +- flatbuffers \ No newline at end of file diff --git a/Source/library/FBSUtil/flatcc/LICENSE b/Source/library/FBSUtil/flatcc/LICENSE new file mode 100644 index 0000000..48b3016 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Mikkel F. Jørgensen, dvide.com + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/Source/library/FBSUtil/flatcc/README.md b/Source/library/FBSUtil/flatcc/README.md new file mode 100644 index 0000000..fb6c79f --- /dev/null +++ b/Source/library/FBSUtil/flatcc/README.md @@ -0,0 +1,4 @@ +# Flatcc codebase +Clip from [dvidelabs/flatcc](https://github.com/dvidelabs/flatcc) + +Copyright 2015 Mikkel F. Jørgensen, dvide.com \ No newline at end of file diff --git a/Source/library/FBSUtil/flatcc/flatcc.h b/Source/library/FBSUtil/flatcc/flatcc.h new file mode 100644 index 0000000..4f69766 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc.h @@ -0,0 +1,255 @@ +#ifndef FLATCC_H +#define FLATCC_H + +/* + * This is the primary `flatcc` interface when compiling `flatcc` as a + * library. Functions and types in the this interface will be kept + * stable to the extend possible or reasonable, but do not rely on other + * interfaces except "config.h" used to set default options for this + * interface. + * + * This interface is unrelated to the standalone flatbuilder library + * which has a life of its own. + */ + +#ifndef UINT8_MAX +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4820) /* x bytes padding added in struct */ +#endif + +typedef struct flatcc_options flatcc_options_t; +typedef void (*flatcc_error_fun) (void *err_ctx, const char *buf, int len); + +struct flatcc_options { + size_t max_schema_size; + int max_include_depth; + int max_include_count; + int disable_includes; + int allow_boolean_conversion; + int allow_enum_key; + int allow_enum_struct_field; + int allow_multiple_key_fields; + int allow_scan_for_all_fields; + int allow_string_key; + int allow_struct_field_deprecate; + int allow_struct_field_key; + int allow_struct_root; + int ascending_enum; + int hide_later_enum; + int hide_later_struct; + int offset_size; + int voffset_size; + int utype_size; + int bool_size; + int require_root_type; + int strict_enum_init; + uint64_t vt_max_count; + + const char *default_schema_ext; + const char *default_bin_schema_ext; + const char *default_bin_ext; + + /* Code Generator specific options. */ + int gen_stdout; + int gen_dep; + + const char *gen_depfile; + const char *gen_deptarget; + const char *gen_outfile; + + int gen_append; + + int cgen_pad; + int cgen_sort; + int cgen_pragmas; + + int cgen_common_reader; + int cgen_common_builder; + int cgen_reader; + int cgen_builder; + int cgen_verifier; + int cgen_json_parser; + int cgen_json_printer; + int cgen_recursive; + int cgen_spacing; + + int bgen_bfbs; + int bgen_qualify_names; + int bgen_length_prefix; + + /* Namespace args - these can override defaults so are null by default. */ + const char *ns; + const char *nsc; + + const char **inpaths; + const char **srcpaths; + int inpath_count; + int srcpath_count; + const char *outpath; +}; + +/* Runtime configurable optoins. */ +void flatcc_init_options(flatcc_options_t *opts); + +typedef void *flatcc_context_t; + +/* + * Call functions below in order listed one at a time. + * Each parse requires a new context. + * + * A reader file is named after the source base name, e.g. + * `monster.fbs` becomes `monster.h`. Builders are optional and created + * as `monster_builder.h`. A reader require a common header + * `flatbuffers_commoner.h` and a builder requires + * `flatbuffers_common_builder.h` in addition to the reader filers. A + * reader need no other source, but builders must link with the + * `flatbuilder` library and include files in `include/flatbuffers`. + * + * All the files may also be concatenated into one single file and then + * files will not be attempted included externally. This can be used + * with stdout output. The common builder can follow the common + * reader immediately, or at any later point before the first builder. + * The common files should only be included once, but not harm is done + * if duplication occurs. + * + * The outpath is prefixed every output filename. The containing + * directory must exist, but the prefix may have text following + * the directory, for example the namespace. If outpath = "stdout", + * files are generated to stdout. + * + * Note that const char * options must remain valid for the lifetime + * of the context since they are not copied. The options object itself + * is not used after initialization and may be reused. +*/ + +/* + * `name` is the name of the schema file or buffer. If it is path, the + * basename is extracted (leading path stripped), and the default schema + * extension is stripped if present. The resulting name is used + * internally when generating output files. Typically the `name` + * argument will be the same as a schema file path given to + * `flatcc_parse_file`, but it does not have to be. + * + * `name` may be null if only common files are generated. + * + * `error_out` is an optional error handler. If null output is truncated + * to a reasonable size and sent to stderr. `error_ctx` is provided as + * first argument to `error_out` if `error_out` is non-zero, otherwise + * it is ignored. + * + * Returns context or null on error. + */ +flatcc_context_t flatcc_create_context(flatcc_options_t *options, const char *name, + flatcc_error_fun error_out, void *error_ctx); + +/* Like `flatcc_create_context`, but with length argument for name. */ +/* + * Parse is optional - not needed for common files. If the input buffer version + * is called, the buffer must be zero terminated, otherwise an input + * path can be specified. The output path can be null. + * + * Only one parse can be called per context. + * + * The buffer size is limited to the max_schema_size option unless it is + * 0. The default is reasonable size like 64K depending on config flags. + * + * The buffer must remain valid for the duration of the context. + * + * The schema cannot contain include statements when parsed as a buffer. + * + * Returns 0 on success. + */ +int flatcc_parse_buffer(flatcc_context_t ctx, const char *buf, size_t buflen); + +/* + * If options contain a non-zero `inpath` option, the resulting filename is + * prefixed with that path unless the filename is an absolute path. + * + * Errors are sent to the error handler given during initialization, + * or to stderr. + * + * The file size is limited to the max_schema_size option unless it is + * 0. The default is reasonable size like 64K depending on config flags. + * + * Returns 0 on success. + */ +int flatcc_parse_file(flatcc_context_t ctx, const char *filename); + +/* + * Generate output files. The basename derived when the context was + * created is used used to name the output files with respective + * extensions. If the outpath option is not null it is prefixed the + * output files. The `cgen_common_reader, cgen_common_builder, + * cgen_reader, and cgen_builder` must be set or reset depending on what + * is to be generated. The common files do not require a parse, and the + * non-common files require a successfull parse or the result is + * undefined. + * + * Unlinke the parser, the code generator produce errors to stderr + * always. These errors are rare, such as using too long namespace + * names. + * + * If the `gen_stdout` option is set, all files are generated to stdout. + * In this case it is unwise to mix C and binary schema output options. + * + * If `bgen_bfbs` is set, a binary schema is generated to a file with + * the `.bfbs` extension. See also `flatcc_generate_binary_schema` for + * further details. Only `flatcc_generate_files` is called via the + * `flatcc` cli command. + * + * The option `bgen_length_prefix` option will cause a length prefix to be + * written to the each output binary schema. This option is only + * understood when writing to files. + * + * Returns 0 on success. + */ +int flatcc_generate_files(flatcc_context_t ctx); + +/* + * Returns a buffer with a binary schema for a previous parse. + * The user is responsible for calling `free` on the returned buffer + * unless it returns 0 on error. + * + * Can be called instead of generate files, before, or after, but a + * schema must be parsed first. + * + * Returns a binary schema in `reflection.fbs` format. Any included + * files will be contained in the schema and there are no separate + * schema files for included schema. + * + * All type names are scoped, mening that they are refixed their + * namespace using `.` as the namespace separator, for example: + * "MyGame.Example.Monster". Note that the this differs from the current + * `flatc` compiler which does not prefix names. Enum names are not + * scoped, but the scope is implied by the containing enum type. + * The option `bgen_qualify_names=0` changes this behavior. + * + * If the default option `ascending_enum` is disabled, the `flatcc` will + * accept duplicate values and overlapping ranges like the C programming + * language. In this case enum values in the binary schema will not be + * searchable. At any rate enum names are not searchable in the current + * schema format. + * + */ +void *flatcc_generate_binary_schema(flatcc_context_t ctx, size_t *size); + +/* + * Similar to `flatcc_generate_binary_schema` but copies the binary + * schema into a user supplied buffer. If the buffer is too small + * the return value will be negative and the buffer content undefined. + */ +int flatcc_generate_binary_schema_to_buffer(flatcc_context_t ctx, void *buf, size_t bufsiz); + +/* Must be called to deallocate resources eventually - it valid but + * without effect to call with a null context. */ +void flatcc_destroy_context(flatcc_context_t ctx); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* FLATCC_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_accessors.h b/Source/library/FBSUtil/flatcc/flatcc_accessors.h new file mode 100644 index 0000000..41d5017 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_accessors.h @@ -0,0 +1,89 @@ +#ifndef FLATCC_ACCESSORS +#define FLATCC_ACCESSORS + +#ifndef UINT8_MAX +#include +#endif + +#define __flatcc_basic_scalar_accessors_impl(N, T, W, E) \ +static inline size_t N ## __size() \ +{ return sizeof(T); } \ +static inline T *N ## __ptr_add(T *p, size_t i) \ +{ return p + i; } \ +static inline const T *N ## __const_ptr_add(const T *p, size_t i) \ +{ return p + i; } \ +static inline T N ## _read_from_pe(const void *p) \ +{ return N ## _cast_from_pe(*(T *)p); } \ +static inline T N ## _read_to_pe(const void *p) \ +{ return N ## _cast_to_pe(*(T *)p); } \ +static inline T N ## _read(const void *p) \ +{ return *(T *)p; } \ +static inline void N ## _write_from_pe(void *p, T v) \ +{ *(T *)p = N ## _cast_from_pe(v); } \ +static inline void N ## _write_to_pe(void *p, T v) \ +{ *(T *)p = N ## _cast_to_pe(v); } \ +static inline void N ## _write(void *p, T v) \ +{ *(T *)p = v; } + +#define __flatcc_define_integer_accessors_impl(N, T, W, E) \ +static inline T N ## _cast_from_pe(T v) \ +{ return (T) E ## W ## toh((uint ## W ## _t)v); } \ +static inline T N ## _cast_to_pe(T v) \ +{ return (T) hto ## E ## W((uint ## W ## _t)v); } \ +static inline T N ## _cast_from_le(T v) \ +{ return (T) le ## W ## toh((uint ## W ## _t)v); } \ +static inline T N ## _cast_to_le(T v) \ +{ return (T) htole ## W((uint ## W ## _t)v); } \ +static inline T N ## _cast_from_be(T v) \ +{ return (T) be ## W ## toh((uint ## W ## _t)v); } \ +static inline T N ## _cast_to_be(T v) \ +{ return (T) htobe ## W((uint ## W ## _t)v); } \ +__flatcc_basic_scalar_accessors_impl(N, T, W, E) + +#define __flatcc_define_real_accessors_impl(N, T, W, E) \ +union __ ## N ## _cast { T v; uint ## W ## _t u; }; \ +static inline T N ## _cast_from_pe(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = E ## W ## toh(x.u); return x.v; } \ +static inline T N ## _cast_to_pe(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = hto ## E ## W(x.u); return x.v; } \ +static inline T N ## _cast_from_le(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = le ## W ## toh(x.u); return x.v; } \ +static inline T N ## _cast_to_le(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = htole ## W(x.u); return x.v; } \ +static inline T N ## _cast_from_be(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = be ## W ## toh(x.u); return x.v; } \ +static inline T N ## _cast_to_be(T v) \ +{ union __ ## N ## _cast x; \ + x.v = v; x.u = htobe ## W(x.u); return x.v; } \ +__flatcc_basic_scalar_accessors_impl(N, T, W, E) + +#define __flatcc_define_integer_accessors(N, T, W, E) \ +__flatcc_define_integer_accessors_impl(N, T, W, E) + +#define __flatcc_define_real_accessors(N, T, W, E) \ +__flatcc_define_real_accessors_impl(N, T, W, E) + +#define __flatcc_define_basic_integer_accessors(NS, TN, T, W, E) \ +__flatcc_define_integer_accessors(NS ## TN, T, W, E) + +#define __flatcc_define_basic_real_accessors(NS, TN, T, W, E) \ +__flatcc_define_real_accessors(NS ## TN, T, W, E) + +#define __flatcc_define_basic_scalar_accessors(NS, E) \ +__flatcc_define_basic_integer_accessors(NS, uint8, uint8_t, 8, E) \ +__flatcc_define_basic_integer_accessors(NS, uint16, uint16_t, 16, E) \ +__flatcc_define_basic_integer_accessors(NS, uint32, uint32_t, 32, E) \ +__flatcc_define_basic_integer_accessors(NS, uint64, uint64_t, 64, E) \ +__flatcc_define_basic_integer_accessors(NS, int8, int8_t, 8, E) \ +__flatcc_define_basic_integer_accessors(NS, int16, int16_t, 16, E) \ +__flatcc_define_basic_integer_accessors(NS, int32, int32_t, 32, E) \ +__flatcc_define_basic_integer_accessors(NS, int64, int64_t, 64, E) \ +__flatcc_define_basic_real_accessors(NS, float, float, 32, E) \ +__flatcc_define_basic_real_accessors(NS, double, double, 64, E) + +#endif /* FLATCC_ACCESSORS */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_builder.h b/Source/library/FBSUtil/flatcc/flatcc_builder.h new file mode 100644 index 0000000..1de4a1f --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_builder.h @@ -0,0 +1,1576 @@ +#ifndef FLATCC_BUILDER_H +#define FLATCC_BUILDER_H + +/** + * Library for building untyped FlatBuffers. Intended as a support + * library for generated C code to produce typed builders, but might + * also be useful in runtime environments and as support for scripting + * languages. + * + * The builder has two API layers: a stack based `start/end` approach, + * and a direct `create`, and they may be fixed freely. The direct + * appraoch may be used as part of more specialized optimizations such + * as rewriting buffers while the stack approach is convenient for state + * machine driven parsers without a stack, or with a very simple stack + * without extra allocations. + * + * The builder emits partial buffer sequences to a user provided emitter + * function and does not require a full buffer reprensenation in memory. + * For this reason it also does not support sorting or other operations + * that requires representing the buffer, but post-processors can easily + * do this, and the generated schema specific code and provide functions + * to handle this. + * + * A custom allocator with a default realloc implementation can place + * restraints on resource consumption and provide initial allocation + * sizes for various buffers and stacks in use. + * + * A buffer under construction uses a virtual address space for the + * completed part of the buffer, starting at 0 and growing in both + * directions, or just down depending on whether vtables should be + * clustered at the end or not. Clustering may help caching and + * preshipping that part of the buffer. + * + * Because an offset cannot be known before its reference location is + * defined, every completed table, vector, etc. returns a reference into + * the virtual address range. If the final buffer keeps the 0 offset, + * these references remain stable an may be used for external references + * into the buffer. + * + * The maximum buffer than can be constructed is in praxis limited to + * half the UOFFSET_MAX size, typically 2^31 bytes, not counting + * clustered vtables that may consume and additional 2^31 bytes + * (positive address range), but in praxis cannot because vtable + * references are signed and thus limited to 2^31 bytes (or equivalent + * depending on the flatbuffer types chosen). + * + * CORRECTION: in various places rules are mentioned about nesting and using + * a reference at most once. In fact, DAG's are also valid flatbuffers. + * This means a reference may be reused as long as each individual use + * obeys the rules and, for example, circular references are not + * constructed (circular types are ok, but objects graphs with cycles + * are not permitted). Be especially aware of the offset vector create + * call which translates the references into offsets - this can be + * reverted by noting the reference in vector and calculate the base + * used for the offset to restore the original references after the + * vector has been emitted. + */ + +#include +#ifndef UINT8_MAX +#include +#endif + +#include "flatcc_flatbuffers.h" +#include "flatcc_emitter.h" + +/* It is possible to enable logging here. */ +#ifndef FLATCC_BUILDER_ASSERT +#define FLATCC_BUILDER_ASSERT(cond, reason) assert(cond) +#endif + +/* + * Eror handling is not convenient and correct use should not cause + * errors beyond possibly memory allocation, but assertions are a + * good way to trace problems. + * + * Note: some internal assertion will remain if disabled. + */ +#ifndef FLATCC_BUILDER_ASSERT_ON_ERROR +#define FLATCC_BUILDER_ASSERT_ON_ERROR 1 +#endif + +/* + * If set, checks user input agains state and returns error, + * otherwise errors are ignored (assuming they won't happen). + * Errors will be asserted if enabled and checks are not skipped. + */ +#ifndef FLATCC_BUILDER_SKIP_CHECKS +#define FLATCC_BUILDER_SKIP_CHECKS 0 +#endif + + +/* + * When adding the same field to a table twice this is either an error + * or the existing field is returned, potentially introducing garbage + * if the type is a vector, table, or string. When implementing parsers + * it may be convenient to not treat this as an error. + */ +#ifndef FLATCC_BUILDER_ALLOW_REPEAT_TABLE_ADD +#define FLATCC_BUILDER_ALLOW_REPEAT_TABLE_ADD 0 +#endif + +/** + * This type must have same size as `flatbuffers_uoffset_t` + * and must be a signed type. + */ +typedef flatbuffers_soffset_t flatcc_builder_ref_t; + +/** + * Virtual tables are off by one to avoid being mistaken for error at + * position 0, and it makes them detectable as such because no other + * reference is uneven. Vtables are emitted at their actual location + * which is one less than the reference value. + */ +typedef flatbuffers_soffset_t flatcc_builder_vt_ref_t; + +typedef flatbuffers_uoffset_t flatcc_builder_identifier_t; + +/** + * Hints to custom allocators so they can provide initial alloc sizes + * etc. There will be at most one buffer for each allocation type per + * flatcc_builder instance. Buffers containing only structs may avoid + * allocation altogether using a `create` call. The vs stack must hold + * vtable entries for all open tables up to their requested max id, but + * unused max id overlap on the stack. The final vtables only store the + * largest id actually added. The fs stack must hold stack frames for + * the nesting levels expected in the buffer, each about 50-100 bytes. + * The ds stack holds open vectors, table data, and nested buffer state. + * `create` calls bypass the `ds` and `fs` stack and are thus faster. + * The vb buffer holds a copy of all vtables seen and emitted since last + * vtable flush. The patch log holds a uoffset for every table field + * added to currently open tables. The hash table holds a uoffset entry + * for each hash slot where the allocator decides how many to provide + * above a certain minimum. The vd buffer allocates vtable descriptors + * which is a reference to an emitted vtable, an offset to a cached + * vtable, and a link to next descriptor with same hash. Calling `reset` + * after build can either keep the allocation levels for the next + * buffer, or reduce the buffers already allocated by requesting 1 byte + * allocations (meaning provide a default). + * + * The user stack is not automatically allocated, but when entered + * explicitly, the boundary is rembered in the current live + * frame. + */ +enum flatcc_builder_alloc_type { + /* The stack where vtables are build. */ + flatcc_builder_alloc_vs, + /* The stack where data structures are build. */ + flatcc_builder_alloc_ds, + /* The virtual table buffer cache, holds a copy of each vt seen. */ + flatcc_builder_alloc_vb, + /* The patch log, remembers table fields with outstanding offset refs. */ + flatcc_builder_alloc_pl, + /* The stack of frames for nested types. */ + flatcc_builder_alloc_fs, + /* The hash table part of the virtual table cache. */ + flatcc_builder_alloc_ht, + /* The vtable descriptor buffer, i.e. list elements for emitted vtables. */ + flatcc_builder_alloc_vd, + /* User stack frame for custom data. */ + flatcc_builder_alloc_us, + + /* Number of allocation buffers. */ + flatcc_builder_alloc_buffer_count +}; + +/** Must reflect the `flatcc_builder_alloc_type` enum. */ +#define FLATCC_BUILDER_ALLOC_BUFFER_COUNT flatcc_builder_alloc_buffer_count + +/** + * Emits data to a conceptual deque by appending to either front or + * back, starting from offset 0. + * + * Each emit call appends a strictly later or earlier sequence than the + * last emit with same offset sign. Thus a buffer is gradually grown at + * both ends. `len` is the combined length of all iov entries such that + * `offset + len` yields the former offset for negative offsets and + * `offset + len` yields the next offset for non-negative offsets. + * The bulk of the data will be in the negative range, possibly all of + * it. The first emitted emitted range will either start or end at + * offset 0. If offset 0 is emitted, it indicates the start of clustered + * vtables. The last positive (non-zero) offset may be zero padding to + * place the buffer in a full multiple of `block_align`, if set. + * + * No iov entry is empty, 0 < iov_count <= FLATCC_IOV_COUNT_MAX. + * + * The source data are in general ephemeral and should be consumed + * immediately, as opposed to caching iov. + * + * For high performance applications: + * + * The `create` calls may reference longer living data, but header + * fields etc. will still be short lived. If an emitter wants to + * reference data in another buffer rather than copying, it should + * inspect the memory range. The length of an iov entry may also be used + * since headers are never very long (anything starting at 16 bytes can + * safely be assumed to be user provided, or static zero padding). It is + * guaranteed that data pointers in `create` calls receive a unique slot + * separate from temporary headers, in the iov table which may be used + * for range checking or hashing (`create_table` is the only call that + * mutates the data buffer). It is also guaranteed (with the exception + * of `create_table` and `create_cached_vtable`) that data provided to + * create calls are not referenced at all by the builder, and these data + * may therefore de-facto be handles rather than direct pointers when + * the emitter and data provider can agree on such a protocol. This does + * NOT apply to any start/end/add/etc. calls which do copy to stack. + * `flatcc_builder_padding_base` may be used to test if an iov entry is + * zero padding which always begins at that address. + * + * Future: the emit interface could be extended with a type code + * and return an existing object insted of the emitted if, for + * example, they are identical. Outside this api level, generated + * code could provide a table comparison function to help such + * deduplication. It would be optional because two equal objects + * are not necessarily identical. The emitter already receives + * one object at time. + * + * Returns 0 on success and otherwise causes the flatcc_builder + * to fail. + */ +typedef int flatcc_builder_emit_fun(void *emit_context, + const flatcc_iovec_t *iov, int iov_count, flatbuffers_soffset_t offset, size_t len); + +/* + * Returns a pointer to static padding used in emitter calls. May + * sometimes also be used for empty defaults such as identifier. + */ +extern const uint8_t flatcc_builder_padding_base[]; + +/** + * `request` is a minimum size to be returned, but allocation is + * expected to grow exponentially or in reasonable chunks. Notably, + * `alloc_type = flatcc_builder_alloc_ht` will only use highest available + * power of 2. The allocator may shrink if `request` is well below + * current size but should avoid repeated resizing on small changes in + * request sizes. If `zero_fill` is non-zero, allocated data beyond + * the current size must be zeroed. The buffer `b` may be null with 0 + * length initially. `alloc_context` is completely implementation + * dependendent, and not needed when just relying on realloc. The + * resulting buffer may be the same or different with moved data, like + * realloc. Returns -1 with unmodified buffer on failure or 0 on + * success. The `alloc_type` identifies the buffer type. This may be + * used to cache buffers between instances of builders, or to decide a + * default allocation size larger than requested. If `need` is zero the + * buffer should be deallocate if non-zero, and return success (0) + * regardless. + */ +typedef int flatcc_builder_alloc_fun(void *alloc_context, + flatcc_iovec_t *b, size_t request, int zero_fill, int alloc_type); + +/* + * The number of hash slots there will be allocated space for. The + * allocator may provide more. The size returned should be + * `sizeof(flatbuffers_uoffset_t) * count`, where the size is a power of + * 2 (or the rest is wasted). The hash table can store many more entries + * than slots using linear search. The table does not resize. + */ +#ifndef FLATCC_BUILDER_MIN_HASH_COUNT +#define FLATCC_BUILDER_MIN_HASH_COUNT 64 +#endif + +typedef struct __flatcc_builder_buffer_frame __flatcc_builder_buffer_frame_t; +struct __flatcc_builder_buffer_frame { + flatcc_builder_identifier_t identifier; + flatcc_builder_ref_t mark; + int flags; + size_t block_align; +}; + +typedef struct __flatcc_builder_vector_frame __flatcc_builder_vector_frame_t; +struct __flatcc_builder_vector_frame { + flatbuffers_uoffset_t elem_size; + flatbuffers_uoffset_t count; + flatbuffers_uoffset_t max_count; +}; + +typedef struct __flatcc_builder_table_frame __flatcc_builder_table_frame_t; +struct __flatcc_builder_table_frame { + flatbuffers_uoffset_t vs_end; + flatbuffers_uoffset_t pl_end; + uint32_t vt_hash; + flatbuffers_voffset_t id_end; +}; + +/* + * Store state for nested structures such as buffers, tables and vectors. + * + * For less busy data and data where access to a previous state is + * irrelevant, the frame may store the current state directly. Otherwise + * the current state is maintained in the flatcc_builder_t structure in a + * possibly derived form (e.g. ds pointer instead of ds_end offset) and + * the frame is used to store the previous state when the frame is + * entered. + * + * Most operations have a start/update/end cycle the decides the + * liftetime of a frame, but these generally also have a direct form + * (create) that does not use a frame at all. These still do some + * state updates notably passing min_align to parent which may also be + * an operation without a frame following the child level operation + * (e.g. create struct, create buffer). Ending a frame results in the + * same kind of updates. + */ +typedef struct __flatcc_builder_frame __flatcc_builder_frame_t; +struct __flatcc_builder_frame { + flatbuffers_uoffset_t ds_first; + flatbuffers_uoffset_t type_limit; + flatbuffers_uoffset_t ds_offset; + uint16_t align; + uint16_t type; + union { + __flatcc_builder_table_frame_t table; + __flatcc_builder_vector_frame_t vector; + __flatcc_builder_buffer_frame_t buffer; + }; +}; + +/** + * The main flatcc_builder structure. Can be stack allocated and must + * be initialized with `flatcc_builder_init` and cleared with + * `flatcc_builder_clear` to reclaim memory. Between buffer builds, + * `flatcc_builder_reset` may be used. + */ +typedef struct flatcc_builder flatcc_builder_t; + +struct flatcc_builder { + /* Next entry on reserved stack in `alloc_pl` buffer. */ + flatbuffers_voffset_t *pl; + /* Next entry on reserved stack in `alloc_vs` buffer. */ + flatbuffers_voffset_t *vs; + /* One above the highest entry in vs, used to track vt_size. */ + flatbuffers_voffset_t id_end; + /* The evolving vtable hash updated with every new field. */ + uint32_t vt_hash; + + /* Pointer to ds_first. */ + uint8_t *ds; + /* Offset from `ds` on current frame. */ + flatbuffers_uoffset_t ds_offset; + /* ds buffer size relative to ds_first, clamped to max size of current type. */ + flatbuffers_uoffset_t ds_limit; + + /* ds_first, ds_first + ds_offset is current ds stack range. */ + flatbuffers_uoffset_t ds_first; + /* Points to currently open frame in `alloc_fs` buffer. */ + __flatcc_builder_frame_t *frame; + + /* Only significant to emitter function, if at all. */ + void *emit_context; + /* Only significant to allocator function, if at all. */ + void *alloc_context; + /* Customizable write function that both appends and prepends data. */ + flatcc_builder_emit_fun *emit; + /* Customizable allocator that also deallocates. */ + flatcc_builder_alloc_fun *alloc; + /* Buffers indexed by `alloc_type` */ + flatcc_iovec_t buffers[FLATCC_BUILDER_ALLOC_BUFFER_COUNT]; + /* Number of slots in ht given as 1 << ht_width. */ + size_t ht_width; + + /* The location in vb to add next cached vtable. */ + flatbuffers_uoffset_t vb_end; + /* Where to allocate next vtable descriptor for hash table. */ + flatbuffers_uoffset_t vd_end; + /* Ensure final buffer is aligned to at least this. Nested buffers get their own `min_align`. */ + uint16_t min_align; + /* The current active objects alignment isolated from nested activity. */ + uint16_t align; + /* The current buffers block alignment used when emitting buffer. */ + uint16_t block_align; + /* Signed virtual address range used for `flatcc_builder_ref_t` and emitter. */ + flatcc_builder_ref_t emit_start; + flatcc_builder_ref_t emit_end; + /* 0 for top level, and end of buffer ref for nested buffers. */ + flatcc_builder_ref_t buffer_mark; + /* Current nesting level. Helpful to state-machines with explicit stack and to check `max_level`. */ + int level; + /* Aggregate check for allocated frame and max_level. */ + int limit_level; + /* Track size prefixed buffer. */ + int buffer_flags; + + /* Settings that may happen with no frame allocated. */ + + flatcc_builder_identifier_t identifier; + + /* Settings that survive reset (emitter, alloc, and contexts also survive): */ + + /* If non-zero, vtable cache gets flushed periodically. */ + size_t vb_flush_limit; + /* If non-zero, fails on deep nesting to help drivers with a stack, such as recursive parsers etc. */ + int max_level; + /* If non-zero, do not cluster vtables at end, only emit negative offsets (0 by default). */ + int disable_vt_clustering; + + /* Set if the default emitter is being used. */ + int is_default_emitter; + /* Only used with default emitter. */ + flatcc_emitter_t default_emit_context; + + /* Offset to the last entered user frame on the user frame stack, after frame header, or 0. */ + size_t user_frame_offset; + + /* The offset to the end of the most recent user frame. */ + size_t user_frame_end; +}; + +/** + * Call this before any other API call. + * + * The emitter handles the completed chunks of the buffer that will no + * longer be required by the builder. It is largely a `write` function + * that can append to both positive and negative offsets. + * + * No memory is allocated during init. Buffers will be allocated as + * needed. The `emit_context` is only used by the emitter, if at all. + * + * `flatcc_builder_reset/clear` calls are automtically forwarded to the + * default emitter. + * + * Returns -1 on failure, 0 on success. + */ +int flatcc_builder_init(flatcc_builder_t *B); + +/** + * Use instead of `flatcc_builder_init` when providing a custom allocator + * or emitter. Leave emitter or allocator null to use default. + * Cleanup of emit and alloc context must be handled manually after + * the builder is cleared or reset, except if emitter is null the + * default will be automatically cleared and reset. + * + * Returns -1 on failure, 0 on success. + */ +int flatcc_builder_custom_init(flatcc_builder_t *B, + flatcc_builder_emit_fun *emit, void *emit_context, + flatcc_builder_alloc_fun *alloc, void *alloc_context); + +/* + * Returns (flatcc_emitter_t *) if the default context is used. + * Other emitter might have null contexts. + */ +void *flatcc_builder_get_emit_context(flatcc_builder_t *B); + +/** + * Prepares builder for a new build. The emitter is not told when a + * buffer is finished or when a new begins, and must be told so + * separately. Allocated buffers will be zeroed, but may optionally be + * reduced to their defaults (signalled by reallocating each non-empty + * buffer to a single byte). General settings are cleared optionally, + * such as cache flushing. Buffer specific settings such as buffer + * identifier are always cleared. + * + * Returns -1 if allocator complains during buffer reduction, 0 on + * success. + */ +int flatcc_builder_custom_reset(flatcc_builder_t *B, + int reduce_buffers, int set_defaults); + +/* + * Same as `flatcc_builder_custom_reset` with default arguments + * where buffers are not reduced and default settings are not reset. + */ +int flatcc_builder_reset(flatcc_builder_t *B); + +/** + * Deallocates all memory by calling allocate with a zero size request + * on each buffer, then zeroing the builder structure itself. + */ +void flatcc_builder_clear(flatcc_builder_t *B); + +/** + * Allocates to next higher power of 2 using system realloc and ignores + * `alloc_context`. Only reduces size if a small subsequent increase in + * size would not trigger a reallocation. `alloc_type` is used to + * set minimum sizes. Hash tables are allocated to the exact requested + * size. See also `alloc_fun`. + */ +int flatcc_builder_default_alloc(void *alloc_context, + flatcc_iovec_t *b, size_t request, int zero_fill, int alloc_type); + +/** + * If non-zero, the vtable cache will get flushed whenever it reaches + * the given limit at a point in time where more space is needed. The + * limit is not exact as it is only tested when reallocation is + * required. + */ +void flatcc_builder_set_vtable_cache_limit(flatcc_builder_t *B, size_t size); + +/** + * Manual flushing of vtable for long running tasks. Mostly used + * internally to deal with nested buffers. + */ +void flatcc_builder_flush_vtable_cache(flatcc_builder_t *B); + +/** + * Low-level support function to aid in constructing nested buffers without + * allocation. Not for regular use. + * + * Call where `start_buffer` would have been placed when using + * `create_buffer` in a nested context. Save the return value on a stack + * as argument to `pop_buffer_alignment`. + * + * The call resets the current derived buffer alignment so the nested + * buffer will not be aligned to more than required. + * + * Often it will not be necessary to be so careful with alignment since + * the alignment cannot be invalid by failing to use push and pop, but + * for code generation it will ensure the correct result every time. + */ +uint16_t flatcc_builder_push_buffer_alignment(flatcc_builder_t *B); + +/** + * Low-level call. + * + * Call with the return value from push_buffer_alignment after a nested + * `create_buffer_call`. The alignments merge back up in the buffer + * hierarchy so the top level buffer gets the largest of all aligments. + */ +void flatcc_builder_pop_buffer_alignment(flatcc_builder_t *B, uint16_t buffer_align); + +/** + * This value may be of interest when the buffer has been ended, for + * example when subsequently allocating memory for the buffer to ensure + * that memory is properly aligned. + */ +uint16_t flatcc_builder_get_buffer_alignment(flatcc_builder_t *B); + +/** + * Level 0 means no buffer is started, otherwise it increments with + * start calls and decrements with end calls (approximately for + * optimized operations such as table vectors). + * + * If `max_level` has been set, `get_level` always returns a value <= + * `max_level` provided no start call has failed. + * + * Level continues to increment inside nested buffers. + */ +int flatcc_builder_get_level(flatcc_builder_t *B); + +/** + * Setting the max level triggers a failure on start of new nestings + * when the level is reached. May be used to protect recursive descend + * parsers etc. or later buffer readers. + * + * The builder itself is not sensitive to depth, and the allocator is a + * better way to protect resource abuse. + * + * `max_level` is not reset inside nested buffers. + */ +void flatcc_builder_set_max_level(flatcc_builder_t *B, int level); + +/** + * By default ordinary data such as tables are placed in front of + * earlier produced content and vtables are placed at the very end thus + * clustering vtables together. This can be disabled so all content is + * placed in front. Nested buffers ignores this setting because they can + * only place content in front because they cannot blend with the + * containing buffers content. Clustering could be more cache friendly + * and also enables pre-shipping of the vtables during transmission. + */ +void flatcc_builder_set_vtable_clustering(flatcc_builder_t *B, int enable); + +enum flatcc_builder_buffer_flags { + flatcc_builder_is_nested = 1, + flatcc_builder_with_size = 2, +}; + +/** + * An alternative to start buffer, start struct/table ... end buffer. + * + * This call is mostly of interest as a means to quicly create a zero + * allocation top-level buffer header following a call to create_struct, + * or to create_vtable/create_table. For that, it is quite simple to + * use. For general buffer construction without allocation, more care is + * needed, as discussed below. + * + * If the content is created with `start/end_table` calls, or similar, + * it is better to use `start/end_buffer` since stack allocation is used + * anyway. + * + * The buffer alignment must be provided manually as it is not derived + * from constructed content, unlike `start/end_buffer`. Typically + * `align` would be same argument as provided to `create_struct`. + * `get_buffer_alignment` may also used (note: `get_buffer_alignment` + * may return different after the call because it will be updated with + * the `block_align` argument to `create_buffer` but that is ok). + * + * The buffer may be constructed as a nested buffer with the `is_nested + * = 1` flag. As a nested buffer a ubyte vector header is placed before + * the aligned buffer header. A top-level buffer will normally have + * flags set to 0. + * + * A top-level buffer may also be constructed with the `with_size = 2` + * flag for top level buffers. It adds a size prefix similar to + * `is_nested` but the size is part of the aligned buffer. A size + * prefixed top level buffer must be accessed with a size prefix aware + * reader, or the buffer given to a standard reader must point to after + * the size field while keeping the buffer aligned to the size field + * (this will depend on the readers API which may be an arbitrary other + * language). + * + * If the `with_size` is used with the `is_nested` flag, the size is + * added as usual and all fields remain aligned as before, but padding + * is adjusted to ensure the buffer is aligned to the size field so + * that, for example, the nested buffer with size can safely be copied + * to a new memory buffer for consumption. + * + * Generally, references may only be used within the same buffer + * context. With `create_buffer` this becomes less precise. The rule + * here is that anything that would be valid with start/end_buffer + * nestings is also valid when removing the `start_buffer` call and + * replacing `end_buffer` with `create_buffer`. + * + * Note the additional burden of tracking buffer alignment manually - + * To help with this use `push_buffer_alignment` where `start_buffer` + * would have been placed, and `pop_buffer_alignment after the + * `create_buffer` call, and use `get_buffer_alignemnt` as described + * above. + * + * `create_buffer` is not suitable as a container for buffers created + * with `start/end_buffer` as these make assumptions about context that + * create buffer does not provide. Also, there is no point in doing so, + * since the idea of `create_buffer` is to avoid allocation in the first + * place. + */ +flatcc_builder_ref_t flatcc_builder_create_buffer(flatcc_builder_t *B, + const char identifier[FLATBUFFERS_IDENTIFIER_SIZE], + uint16_t block_align, + flatcc_builder_ref_t ref, uint16_t align, int flags); + +/** + * Creates a struct within the current buffer without using any + * allocation. Formally the struct should be used as a root in the + * `end_buffer` call as there are no other way to use struct while + * conforming to the FlatBuffer format - noting that tables embed + * structs in their own data area. + * + * The struct should be in little endian format and follow the usual + * FlatBuffers alignment rules, although this API won't care about what + * is being stored. + * + * May also be used to simply emit a struct through the emitter + * interface without being in a buffer and without being a valid + * FlatBuffer. + */ +flatcc_builder_ref_t flatcc_builder_create_struct(flatcc_builder_t *B, + const void *data, size_t size, uint16_t align); + +/** + * Starts a struct and returns a pointer that should be used immediately + * to fill in the struct in protocol endian format, and when done, + * `end_struct` should be called. The returned reference should be used + * as argument to `end_buffer`. See also `create_struct`. + */ +void *flatcc_builder_start_struct(flatcc_builder_t *B, + size_t size, uint16_t align); + +/** + * Return a pointer also returned at start struct, e.g. for endian + * conversion. + */ +void *flatcc_builder_struct_edit(flatcc_builder_t *B); + +/** + * Emits the struct started by `start_struct` and returns a reference to + * be used as root in an enclosing `end_buffer` call. + * As mentioned in `create_struct`, these can also be used more freely, + * but not while being conformant FlatBuffers. + */ +flatcc_builder_ref_t flatcc_builder_end_struct(flatcc_builder_t *B); + +/** + * The buffer always aligns to at least the offset size (typically 4) + * and the internal alignment requirements of the buffer content which + * is derived as content is added. + * + * In addition, block_align can be specified. This ensures the resulting + * buffer is at least aligned to the block size and that the total size + * is zero padded to fill a block multiple if necessary. Because the + * emitter operates on a virtual address range before the full buffer is + * aligned, it may have to make assumptions based on that: For example, + * it may be processing encryption blocks in the fly, and the resulting + * buffer should be aligned to the encryption block size, even if the + * content is just a byte aligned struct. Block align helps ensure this. + * If the block align as 1 there will be no attempt to zero pad at the + * end, but the content may still warrant padding after the header. End + * padding is only needed with clustered vtables (which is the default). + * + * `block_align` is allowed to be 0 meaning it will inherit from parent if + * present, and otherwise it defaults to 1. + * + * The identifier may be null, and it may optionally be set later with + * `set_identifier` before the `end_buffer` call. + * + * General note: + * + * Only references returned with this buffer as current (i.e. last + * unended buffer) can be stored in other objects (tables, offset + * vectors) also belonging to this buffer, or used as the root argument + * to `end_buffer`. A reference may be stored at most once, and unused + * references will result in buffer garbage. All calls must be balanced + * around the respective start / end operations, but may otherwise nest + * freely, including nested buffers. Nested buffers are supposed to be + * stored in a table offset field to comply with FlatBuffers, but the + * API does not place any restrictions on where references are stored, + * as long as they are indicated as offset fields. + * + * All alignment in all API calls must be between 1 and 256 and must be a + * power of 2. This is not checked. Only if explicitly documented can it + * also be 0 for a default value. + * + * `flags` can be `with_size` but `is_nested` is derived from context + * see also `create_buffer`. + */ +int flatcc_builder_start_buffer(flatcc_builder_t *B, + const char identifier[FLATBUFFERS_IDENTIFIER_SIZE], + uint16_t block_align, int flags); + +/** + * The root object should be a struct or a table to conform to the + * FlatBuffers format, but technically it can also be a vector or a + * string, or even a child buffer (which is also vector as seen by the + * buffer). The object must be created within the current buffer + * context, that is, while the current buffer is the deepest nested + * buffer on the stack. + */ +flatcc_builder_ref_t flatcc_builder_end_buffer(flatcc_builder_t *B, flatcc_builder_ref_t root); + +/** + * The embed buffer is mostly intended to add an existing buffer as a + * nested buffer. The buffer will be wrapped in a ubyte vector such that + * the buffer is aligned at vector start, after the size field. + * + * If `align` is 0 it will default to 8 so that all FlatBuffer numeric + * types will be readable. NOTE: generally do not count on align 0 being + * valid or even checked by the API, but in this case it may be + * difficult to know the internal buffer alignment, and 1 would be the wrong + * choice. + * + * If `block_align` is set (non-zero), the buffer is placed in an isolated + * block multiple. This may cost up to almost 2 block sizes in padding. + * If the `block_align` argument is 0, it inherits from the parent + * buffer block_size, or defaults to 1. + * + * The `align` argument must be set to respect the buffers internal + * alignment requirements, but if the buffer is smaller it will not be + * padded to isolate the buffer. For example a buffer of with + * `align = 64` and `size = 65` may share its last 64 byte block with + * other content, but not if `block_align = 64`. + * + * Because the ubyte size field is not, by default, part of the aligned + * buffer, significant space can be wasted if multiple blocks are added + * in sequence with a large block size. + * + * In most cases the distinction between the two alignments is not + * important, but it allows separate configuration of block internal + * alignment and block size, which can be important for auto-generated + * code that may know the alignment of the buffer, but not the users + * operational requirements. + * + * If the buffer is embedded without a parent buffer, it will simply + * emit the buffer through the emit interface, but may also add padding + * up to block alignment. At top-level there will be no size field + * header. + * + * If `with_size` flag is set, the buffer is aligned to size field and + * the above note about padding space no longer applies. The size field + * is added regardless. The `is_nested` flag has no effect since it is + * impplied. + */ +flatcc_builder_ref_t flatcc_builder_embed_buffer(flatcc_builder_t *B, + uint16_t block_align, + const void *data, size_t size, uint16_t align, int flags); + +/** + * Applies to the innermost open buffer. The identifier may be null or + * contain all zero. Overrides any identifier given to the start buffer + * call. + */ +void flatcc_builder_set_identifier(flatcc_builder_t *B, + const char identifier[FLATBUFFERS_IDENTIFIER_SIZE]); + +enum flatcc_builder_type { + flatcc_builder_empty = 0, + flatcc_builder_buffer, + flatcc_builder_struct, + flatcc_builder_table, + flatcc_builder_vector, + flatcc_builder_offset_vector, + flatcc_builder_string +}; + +/** + * Returns the object type currently on the stack, for example if + * needing to decide how to close a buffer. Because a table is + * automatically added when starting a table buffer, + * `flatcc_builder_table_buffer` should not normally be seen and the level + * should be 2 before when closing a top-level table buffer, and 0 + * after. A `flatcc_builder_struct_buffer` will be visible at level 1. + * + */ +enum flatcc_builder_type flatcc_builder_get_type(flatcc_builder_t *B); + +/** + * Similar to `get_type` but for a specific level. `get_type_at(B, 1)` + * will return `flatcc_builder_table_buffer` if this is the root buffer + * type. get_type_at(B, 0) is always `flatcc_builder_empty` and so are any + * level above `get_level`. + */ +enum flatcc_builder_type flatcc_builder_get_type_at(flatcc_builder_t *B, int level); + +/** + * The user stack is available for custom data. It may be used as + * a simple stack by extending or reducing the inner-most frame. + * + * A frame has a size and a location on the user stack. Entering + * a frame ensures the start is aligned to sizeof(size_t) and + * ensures the requested space is available without reallocation. + * When exiting a frame, the previous frame is restored. + * + * A user frame works completely independently of the builders + * frame stack for tracking tables vectors etc. and does not have + * to be completely at exit, but obviously it is not valid to + * exit more often the entered. + * + * The frame is zeroed when entered. + * + * The returned pointer is only valid until the next call to + * `enter/extend_user_frame`. The latest frame pointer + * can be restored by calling `get_user_frame`. + */ +void *flatcc_builder_enter_user_frame(flatcc_builder_t *B, size_t size); + +/** + * Makes the parent user frame current, if any. It is not valid to call + * if there isn't any frame. + */ +void flatcc_builder_exit_user_frame(flatcc_builder_t *B); + +/** + * Returns a pointer to the start of the inner-most user frame. It is + * not valid to call if there isn't any fram. + */ +void *flatcc_builder_get_user_frame(flatcc_builder_t *B); + +/** + * Returns 1 if there is a user frame, and 0 otherwise. + */ +int flatcc_builder_has_user_frame(flatcc_builder_t *B); + + +/** + * Returns the size of the buffer and the logical start and end address + * of with respect to the emitters address range. `end` - `start` also + * yields the size. During construction `size` is the emitted number of + * bytes and after buffer close it is the actual buffer size - by then + * the start is also the return value of close buffer. End marks the end + * of the virtual table cluster block. + * + * NOTE: there is no guarantee that all vtables end up in the cluster + * block if there is placed a limit on the vtable size, or if nested + * buffers are being used. On the other hand, if these conditions are + * met, it is guaranteed that all vtables are present if the vtable + * block is available (this depends on external transmission - the + * vtables are always emitted before tables using them). In all cases + * the vtables will behave as valid vtables in a flatbuffer. + */ +size_t flatcc_builder_get_buffer_size(flatcc_builder_t *B); + +/** + * Returns the reference to the start of the emitter buffer so far, or + * in total after buffer end, in the virtual address range used + * by the emitter. Start is also returned by buffer end. + */ +flatcc_builder_ref_t flatcc_builder_get_buffer_start(flatcc_builder_t *B); + +/** + * Returns the reference to the end of buffer emitted so far. When + * clustering vtables, this is the end of tables, or after buffer end, + * also zero padding if block aligned. If clustering is disabled, this + * method will return 0 as the buffer only grows down then. + */ +flatcc_builder_ref_t flatcc_builder_get_buffer_mark(flatcc_builder_t *B); + +/** + * Creates the vtable in the current buffer context, somewhat similar to + * how create_vector operates. Each call results in a new table even if + * an identical has already been emitted. + * + * Also consider `create_cached_vtable` which will reuse existing + * vtables. + * + * This is low-low-level function intended to support + * `create_cached_vtable` or equivalent, and `create_table`, both of + * which are normally used indirectly via `start_table`, `table_add`, + * `table_add_offset`..., `table_end`. + * + * Creates a vtable as a verbatim copy. This means the vtable must + * include the header fields containing the vtable size and the table + * size in little endian voffset_t encoding followed by the vtable + * entries in same encoding. + * + * The function may be used to copy vtables from other other buffers + * since they are directly transferable. + * + * The returned reference is actually the emitted location + 1. This + * ensures the vtable is not mistaken for error because 0 is a valid + * vtable reference. `create_table` is aware of this and substracts one + * before computing the final offset relative to the table. This also + * means vtable references are uniquely identifiable by having the + * lowest bit set. + * + * vtable references may be reused within the same buffer, not any + * parent or other related buffer (technically this is possible though, + * as long as it is within same builder context, but it will not construct + * valid FlatBuffers because the buffer cannot be extracted in isolation). + */ +flatcc_builder_vt_ref_t flatcc_builder_create_vtable(flatcc_builder_t *B, + const flatbuffers_voffset_t *vt, + flatbuffers_voffset_t vt_size); + +/** + * Support function to `create_vtable`. See also the uncached version + * `create_vtable`. + * + * Looks up the constructed vtable on the vs stack too see if it matches + * a cached entry. If not, it emits a new vtable either at the end if + * top-level and clustering is enabled, or at the front (always for + * nested buffers). + * + * If the same vtable was already emitted in a different buffer, but not + * in the current buffer, the cache entry will be reused, but a new + * table will be emitted the first it happens in the same table. + * + * The returned reference is + 1 relative to the emitted address range + * to identify it as a vtable and to avoid mistaking the valid 0 + * reference for an error (clustered vtables tend to start at the end at + * the virtual address 0, and up). + * + * The hash function can be chosen arbitrarily but may result in + * duplicate emitted vtables if different hash functions are being used + * concurrently, such as mixing the default used by `start/end table` + * with a custom function (this is not incorrect, it only increases the + * buffer size and cache pressure). + * + * If a vtable has a unique ID by other means than hashing the content, + * such as an integer id, and offset into another buffer, or a pointer, + * a good hash may be multiplication by a 32-bit prime number. The hash + * table is not very sensitive to collissions as it uses externally + * chained hashing with move to front semantics. + */ +flatcc_builder_vt_ref_t flatcc_builder_create_cached_vtable(flatcc_builder_t *B, + const flatbuffers_voffset_t *vt, + flatbuffers_voffset_t vt_size, uint32_t vt_hash); + +/* + * Based on Knuth's prime multiplier. + * + * This is an incremental hash that is called with id and size of each + * non-empty field, and finally with the two vtable header fields + * when vtables are constructed via `table_add/table_add_offset`. + * + */ +#ifndef FLATCC_SLOW_MUL +#ifndef FLATCC_BUILDER_INIT_VT_HASH +#define FLATCC_BUILDER_INIT_VT_HASH(hash) { (hash) = (uint32_t)0x2f693b52UL; } +#endif +#ifndef FLATCC_BUILDER_UPDATE_VT_HASH +#define FLATCC_BUILDER_UPDATE_VT_HASH(hash, id, offset) \ + { (hash) = (((((uint32_t)id ^ (hash)) * (uint32_t)2654435761UL)\ + ^ (uint32_t)(offset)) * (uint32_t)2654435761UL); } +#endif +#ifndef FLATCC_BUILDER_BUCKET_VT_HASH +#define FLATCC_BUILDER_BUCKET_VT_HASH(hash, width) (((uint32_t)(hash)) >> (32 - (width))) +#endif +#endif + +/* + * By default we use Bernsteins hash as fallback if multiplication is slow. + * + * This just have to be simple, fast, and work on devices without fast + * multiplication. We are not too sensitive to collisions. Feel free to + * experiment and replace. + */ +#ifndef FLATCC_BUILDER_INIT_VT_HASH +#define FLATCC_BUILDER_INIT_VT_HASH(hash) { (hash) = 5381; } +#endif +#ifndef FLATCC_BUILDER_UPDATE_VT_HASH +#define FLATCC_BUILDER_UPDATE_VT_HASH(hash, id, offset) \ + { (hash) = ((((hash) << 5) ^ (id)) << 5) ^ (offset); } +#endif +#ifndef FLATCC_BUILDER_BUCKET_VT_HASH +#define FLATCC_BUILDER_BUCKET_VT_HASH(hash, width) (((1 << (width)) - 1) & (hash)) +#endif + + + +/** + * Normally use `start_table` instead of this call. + * + * This is a low-level call only intended for high-performance + * applications that repeatedly churn about similar tables of known + * layout, or as a support layer for other builders that maintain their + * own allocation rather than using the stack of this builder. + * + * Creates a table from an already emitted vtable, actual data that is + * properly aligned relative to data start and in little endian + * encoding. Unlike structs, tables can have offset fields. These must + * be stored as flatcc_builder_ref_t types (which have uoffset_t size) as + * returned by the api in native encoding. The `offsets` table contain + * voffsets relative to `data` start (this is different from how vtables + * store offsets because they are relative to a table header). The + * `offsets` table is only used temporarily to translate the stored + * references and is not part of final buffer content. `offsets` may be + * null if `offset_count` is 0. `align` should be the highest aligned + * field in the table, but `size` need not be a multiple of `align`. + * Aside from endian encoding, the vtable must record a table size equal + * to `size + sizeof(flatbuffers_uoffset_t)` because it includes the + * table header field size. The vtable is not accessed by this call (nor + * is it available). Unlike other references, the vtable reference may + * be shared between tables in the same buffer (not with any related + * buffer such as a parent buffer). + * + * The operation will not use any allocation, but will update the + * alignment of the containing buffer if any. + * + * Note: unlike other create calls, except `create_offset_vector`, + * the source data is modified in order to translate references intok + * offsets before emitting the table. + */ +flatcc_builder_ref_t flatcc_builder_create_table(flatcc_builder_t *B, + const void *data, size_t size, uint16_t align, + flatbuffers_voffset_t *offsets, int offset_count, + flatcc_builder_vt_ref_t vt_ref); + +/** + * Starts a table, typically following a start_buffer call as an + * alternative to starting a struct, or to create table fields to be + * stored in a parent table, or in an offset vector. + * A number of `table_add` and table_add_offset` call may be placed + * before the `end_table` call. Struct fields should NOT use `struct` + * related call (because table structs are in-place), rather they should + * use the `table_add` call with the appropriate size and alignment. + * + * A table, like other reference returning calls, may also be started + * outside a buffer if the buffer header and alignment is of no + * interest to the application, for example as part of an externally + * built buffer. + * + * `count` must be larger than the largest id used for this table + * instance. Normally it is set to the number of fields defined in the + * schema, but it may be less if memory is constrained and only few + * fields with low valued id's are in use. The count can extended later + * with `reserve_table` if necessary. `count` may be also be set to a + * large enough value such as FLATBUFFERS_ID_MAX + 1 if memory is not a + * concern (reserves about twice the maximum vtable size to track the + * current vtable and voffsets where references must be translated to + * offsets at table end). `count` may be zero if for example + * `reserve_table` is being used. + * + * Returns -1 on error, 0 on success. + */ +int flatcc_builder_start_table(flatcc_builder_t *B, int count); + +/** + * Call before adding a field with an id that is not below the count set + * at table start. Not needed in most cases. For performance reasons + * the builder does not check all bounds all the the time, but the user + * can do so if memory constraints prevent start_table from using a + * conservative value. See also `table_start`. + * + * Note: this call has absolutely no effect on the table layout, it just + * prevents internal buffer overruns. + * + * Returns -1 on error, 0 on success. + */ +int flatcc_builder_reserve_table(flatcc_builder_t *B, int count); + +/** + * Completes the table constructed on the internal stack including + * emitting a vtable, or finding a matching vtable that has already been + * emitted to the same buffer. (Vtables cannot be shared between + * buffers, but they can between tables of the same buffer). + * + * Note: there is a considerable, but necessary, amount of bookkeeping + * involved in constructing tables. The `create_table` call is much + * faster, but it also expects a lot of work to be done already. + * + * Tables can be created with no fields added. This will result in an + * empty vtable and a table with just a vtable reference. If a table is + * used as a sub-table, such a table might also not be stored at all, + * but we do not return a special reference for that, nor do we provide + * and option to not create the table in this case. This may be + * interpreted as the difference between a null table (not stored in + * parent), and an empty table with a unique offset (and thus identity) + * different from other empty tables. + */ +flatcc_builder_ref_t flatcc_builder_end_table(flatcc_builder_t *B); + +/** + * Optionally this method can be called just before `flatcc_builder_end_table` + * to verify that all required fields have been set. + * Each entry is a table field id. + * + * Union fields should use the type field when checking for presence and + * may also want to check the soundness of the union field overall using + * `check_union_field` with the id one higher than the type field id. + * + * This funcion is typically called by an assertion in generated builder + * interfaces while release builds may want to avoid this performance + * overhead. + * + * Returns 1 if all fields are matched, 0 otherwise. + */ +int flatcc_builder_check_required(flatcc_builder_t *B, const flatbuffers_voffset_t *required, int count); + +/** + * Same as `check_required` when called with a single element. + * + * Typically used when direct calls are more convenient than building an + * array first. Useful when dealing with untrusted intput such as parsed + * text from an external source. + */ +int flatcc_builder_check_required_field(flatcc_builder_t *B, flatbuffers_voffset_t id); + +/** + * Checks that a union field is valid. + * + * The criteria is: + * + * If the type field is not present (at id - 1), or it holds a zero value, + * then the table field (at id) must be present. + * + * Generated builder code may be able to enforce valid unions without + * this check by setting both type and table together, but e.g. parsers + * may receive the type and the table independently and then it makes + * sense to validate the union fields before table completion. + * + * Note that an absent union field is perfectly valid. If a union is + * required, the type field (id - 1), should be checked separately + * while the table field should only be checked here because it can + * (and must) be absent when the type is NONE (= 0). + */ +int flatcc_builder_check_union_field(flatcc_builder_t *B, flatbuffers_voffset_t id); + +/** + * A struct, enum or scalar added should be stored in little endian in + * the return pointer location. The pointer is short lived and will + * not necessarily survive other builder calls. + * + * A union type field can also be set using this call. In fact, this is + * the only way to deal with unions via this API. Consequently, it is + * the users repsonsibility to ensure the appropriate type is added + * at the next higher id. + * + * Null and default values: + * + * FlatBuffers does not officially provide an option for null values + * because it does not distinguish between default values and values + * that are not present. At this api level, we do not deal with defaults + * at all. Callee should test the stored value against the default value + * and only add the field if it does not match the default. This only + * applies to scalar and enum values. Structs cannot have defaults so + * their absence means null, and strings, vectors and subtables do have + * natural null values different from the empty object and empty objects + * with different identity is also possible. + * + * To handle Null for scalars, the following approach is recommended: + * + * Provide a schema-specific `add` operation that only calls this + * low-level add method if the default does not match, and also provide + * another `set` operation that always stores the value, regardless of + * default. For most readers this will be transparent, except for extra + * space used, but for Null aware readers, these can support operations + * to test for Null/default/other value while still supporting the + * normal read operation that returns default when a value is absent + * (i.e. Null). + * + * It is valid to call with a size of 0 - the effect being adding the + * vtable entry. The call may also be dropped in this case to reduce + * the vtable size - the difference will be in null detection. + */ +void *flatcc_builder_table_add(flatcc_builder_t *B, int id, size_t size, uint16_t align); + +/** + * Returns a pointer to the buffer holding the last field added. The + * size argument must match the field size added. May, for example, be + * used to perform endian conversion after initially updating field + * as a native struct. Must be called before the table is ended. + */ +void *flatcc_builder_table_edit(flatcc_builder_t *B, size_t size); + +/** + * Similar to `table_add` but copies source data into the buffer before + * it is returned. Useful when adding a larger struct already encoded in + * little endian. + */ +void *flatcc_builder_table_add_copy(flatcc_builder_t *B, int id, const void *data, size_t size, uint16_t align); + +/** + * Add a string, vector, or sub-table depending on the type if the + * field identifier. The offset ref obtained when the field object was + * closed should be stored as is in the given pointer. The pointer + * is only valid short term, so create the object before calling + * add to table, but the owner table can be started earlier. Never mix + * refs from nested buffers with parent buffers. + * + * Also uses this method to add nested buffers. A nested buffer is + * simple a buffer created while another buffer is open. The buffer + * close operation provides the necessary reference. + * + * When the table closes, all references get converted into offsets. + * Before that point, it is not required that the offset is written + * to. + */ +flatcc_builder_ref_t *flatcc_builder_table_add_offset(flatcc_builder_t *B, int id); + +/** + * Creates a vector in a single operation using an externally supplied + * buffer. This completely bypasses the stack, but the size must be + * known and the content must be little endian. Do not use for strings + * and offset vectors. Other flatbuffer vectors could be used as a + * source, but the length prefix is not required. + * + * Set `max_count` to `FLATBUFFERS_COUNT_MAX(elem_size)` before a call + * to any string or vector operation to the get maximum safe vector + * size, or use (size_t)-1 if overflow is not a concern. + * + * The max count property is a global property that remains until + * explicitly changed. + * + * `max_count` is to prevent malicous or accidental overflow which is + * difficult to detect by multiplication alone, depending on the type + * sizes being used and having `max_count` thus avoids a division for + * every vector created. `max_count` does not guarantee a vector will + * fit in an empty buffer, it just ensures the internal size checks do + * not overflow. A safe, sane limit woud be max_count / 4 because that + * is half the maximum buffer size that can realistically be + * constructed, corresponding to a vector size of `UOFFSET_MAX / 4` + * which can always hold the vector in 1GB excluding the size field when + * sizeof(uoffset_t) = 4. + */ +flatcc_builder_ref_t flatcc_builder_create_vector(flatcc_builder_t *B, + const void *data, size_t count, size_t elem_size, uint16_t align, size_t max_count); + +/** + * Starts a vector on the stack. + * + * Do not use these calls for string or offset vectors, but do store + * scalars, enums and structs, always in little endian encoding. + * + * Use `extend_vector` subseequentlu to add zero, one or more elements + * at time. + * + * See `create_vector` for `max_count` argument (strings and offset + * vectors have a fixed element size and does not need this argument). + * + * Returns 0 on success. + */ +int flatcc_builder_start_vector(flatcc_builder_t *B, size_t elem_size, + uint16_t align, size_t max_count); + +/** + * Emits the vector constructed on the stack by start_vector. + * + * The vector may be accessed in the emitted stream using the returned + * reference, even if the containing buffer is still under construction. + * This may be useful for sorting. This api does not support sorting + * because offset vectors cannot read their references after emission, + * and while plain vectors could be sorted, it has been chosen that this + * task is better left as a separate processing step. Generated code can + * provide sorting functions that work on final in-memory buffers. + */ +flatcc_builder_ref_t flatcc_builder_end_vector(flatcc_builder_t *B); + +/** Returns the number of elements currently on the stack. */ +size_t flatcc_builder_vector_count(flatcc_builder_t *B); + +/** + * Returns a pointer ot the first vector element on stack, + * accessible up to the number of elements currently on stack. + */ +void *flatcc_builder_vector_edit(flatcc_builder_t *B); + +/** + * Similar to `end_vector` but updates all stored references so they + * become offsets to the vector start. + */ +flatcc_builder_ref_t flatcc_builder_end_offset_vector(flatcc_builder_t *B); + +/** Returns the number of elements currently on the stack. */ +size_t flatcc_builder_offset_vector_count(flatcc_builder_t *B); + +/** + * Returns a pointer ot the first vector element on stack, + * accessible up to the number of elements currently on stack. + */ +void *flatcc_builder_offset_vector_edit(flatcc_builder_t *B); + + +/** + * Returns a zero initialized buffer to a new region of the vector which + * is extended at the end. The buffer must be consumed before other api + * calls that may affect the stack, including `extend_vector`. + * + * Do not use for strings or offset vectors. May be used for nested + * buffers, but these have dedicated calls to provide better alignment. + */ +void *flatcc_builder_extend_vector(flatcc_builder_t *B, size_t count); + +/** + * A specialized `vector_extend` that pushes a single element. + * + * Returns the buffer holding a modifiable copy of the added content, + * or null on error. Note: for structs, care must be taken to ensure + * the source has been zero padded. For this reason it may be better to + * use extend(B, 1) and assign specific fields instead. + */ +void *flatcc_builder_vector_push(flatcc_builder_t *B, const void *data); + +/** + * Pushes multiple elements at a time. + * + * Returns the buffer holding a modifiable copy of the added content, + * or null on error. + */ +void *flatcc_builder_append_vector(flatcc_builder_t *B, const void *data, size_t count); + +/** + * Removes elements already added to vector that has not been ended. + * For example, a vector of parsed list may remove the trailing comma, + * or the vector may simply overallocate to get some temporary working + * space. The total vector size must never become negative. + * + * Returns -1 if the count as larger than current count, or 0 on success. + */ +int flatcc_builder_truncate_vector(flatcc_builder_t *B, size_t count); + +/* + * Similar to `create_vector` but with references that get translated + * into offsets. The references must, as usual, belong to the current + * buffer. Strings, scalar and struct vectors can emit directly without + * stack allocation, but offset vectors must translate the offsets + * and therefore need the temporary space. Thus, this function is + * roughly equivalent to to start, append, end offset vector. + * + * See also `flatcc_builder_create_offset_vector_direct`. + */ +flatcc_builder_ref_t flatcc_builder_create_offset_vector(flatcc_builder_t *B, + const flatcc_builder_ref_t *data, size_t count); + +/* + * NOTE: this call takes non-const source vector of references + * and destroys the content. + * + * This is a faster version of `create_offset_vector` where the + * source references are destroyed. In return the vector can be + * emitted directly without passing over the stack. + */ +flatcc_builder_ref_t flatcc_builder_create_offset_vector_direct(flatcc_builder_t *B, + flatcc_builder_ref_t *data, size_t count); + + +/** + * Starts a vector holding offsets to tables or strings. Before + * completion it will hold `flatcc_builder_ref_t` references because the + * offset is not known until the vector start location is known, which + * depends to the final size, which for parsers is generally unknown. + */ +int flatcc_builder_start_offset_vector(flatcc_builder_t *B); + +/** + * Similar to `extend_vector` but returns a buffer indexable as + * `flatcc_builder_ref_t` array. All elements must be set to a valid + * unique non-null reference, but truncate and extend may be used to + * perform edits. All produced references must be stored at most + * once in the buffer, including vectors. There are no checks, and this + * is an easy place to make mistakes. Unused references will leave + * garbage in the buffer. References should not originate from any other + * buffer than the current, including parents and nested buffers. + */ +flatcc_builder_ref_t *flatcc_builder_extend_offset_vector(flatcc_builder_t *B, size_t count); + +/** Similar to truncate_vector. */ +int flatcc_builder_truncate_offset_vector(flatcc_builder_t *B, size_t count); + +/** + * A specialized extend that pushes a single element. + * + * Returns the buffer holding a modifiable copy of the added content, + * or null on error. + */ +flatcc_builder_ref_t *flatcc_builder_offset_vector_push(flatcc_builder_t *B, + flatcc_builder_ref_t ref); + +/** + * Takes an array of refs as argument to do a multi push operation. + * + * Returns the buffer holding a modifiable copy of the added content, + * or null on error. + */ +flatcc_builder_ref_t *flatcc_builder_append_offset_vector(flatcc_builder_t *B, + const flatcc_builder_ref_t *refs, size_t count); + +/** + * Faster string operation that avoids temporary stack storage. The + * string is not required to be zero-terminated, but is expected + * (unchecked) to be utf-8. Embedded zeroes would be allowed but + * ubyte vectors should be used for that. The resulting string will + * have a zero termination added, not included in length. + */ +flatcc_builder_ref_t flatcc_builder_create_string(flatcc_builder_t *B, + const char *s, size_t len); + +/** `create_string` up to zero termination of source. */ +flatcc_builder_ref_t flatcc_builder_create_string_str(flatcc_builder_t *B, + const char *s); + +/** + * `create_string` up to zero termination or at most max_len of source. + * + * Note that like `strncpy` it will include `max_len` characters if + * the source is longer than `max_len`, but unlike `strncpy` it will + * always add zero termination. + */ +flatcc_builder_ref_t flatcc_builder_create_string_strn(flatcc_builder_t *B, const char *s, size_t max_len); + +/** + * Starts an empty string that can be extended subsequently. + * + * While the string is being created, it is guaranteed that there is + * always a null character after the end of the current string length. + * This also holds after `extend` and `append` operations. It is not + * allowed to modify the null character. + * + * Returns 0 on success. + */ +int flatcc_builder_start_string(flatcc_builder_t *B); + +/** + * Similar to `extend_vector` except for the buffer return type and a + * slight speed advantage. Strings are expected to contain utf-8 content + * but this isn't verified, and null characters would be accepted. The + * length is given in bytes. + * + * Appending too much, then truncating can be used to trim string + * escapes during parsing, or convert between unicode formats etc. + */ +char *flatcc_builder_extend_string(flatcc_builder_t *B, size_t len); + +/** + * Concatenes a length of string. If the string contains zeroes (which + * it formally shouldn't), they will be copied in. + * + * Returns the buffer holding a modifiable copy of the added content, + * or null on error. + */ +char *flatcc_builder_append_string(flatcc_builder_t *B, const char *s, size_t len); + +/** `append_string` up to zero termination of source. */ +char *flatcc_builder_append_string_str(flatcc_builder_t *B, const char *s); + +/** `append_string` up zero termination or at most max_len of source. */ +char *flatcc_builder_append_string_strn(flatcc_builder_t *B, const char *s, size_t max_len); + +/** + * Similar to `truncate_vector` available for consistency and a slight + * speed advantage. Reduces string by `len` bytes - it does not set + * the length. The resulting length must not become negative. Zero + * termination is not counted. + * + * Returns -1 of the length becomes negative, 0 on success. + */ +int flatcc_builder_truncate_string(flatcc_builder_t *B, size_t len); + +/** + * Similar to `end_vector` but adds a trailing zero not included + * in the length. The trailing zero is added regardless of whatever + * zero content may exist in the provided string (although it + * formally should not contain any). + */ +flatcc_builder_ref_t flatcc_builder_end_string(flatcc_builder_t *B); + +/** Returns the length of string currently on the stack. */ +size_t flatcc_builder_string_len(flatcc_builder_t *B); + +/** + * Returns a ponter to the start of the string + * accessible up the length of string currently on the stack. + */ +char *flatcc_builder_string_edit(flatcc_builder_t *B); + + +/* + * Only for use with the default emitter. + * + * Fast acces to small buffers from default emitter. + * + * Only valid for default emitters before `flatcc_builder_clear`. The + * return buffer is not valid after a call to `flatcc_builder_reset` or + * `flatcc_builder_clear`. + * + * Returns null if the buffer size is too large to a have a linear + * memory representation or if the emitter is not the default. A valid + * size is between half and a full emitter page size depending on vtable + * content. + * + * Non-default emitters must be accessed by means specific to the + * particular emitter. + * + * If `size_out` is not null, it is set to the buffer size, or 0 if + * operation failed. + * + * The returned buffer should NOT be deallocated explicitly. + * + * The buffer size is the size reported by `flatcc_builder_get_buffer_size`. + */ +void *flatcc_builder_get_direct_buffer(flatcc_builder_t *B, size_t *size_out); + +/* + * Only for use with the default emitter. + * + * Default finalizer that allocates a buffer from the default emitter. + * + * Returns null if memory could not be allocated or if the emitter is + * not the default. This is just a convenience method - there are many + * other possible ways to extract the result of the emitter depending on + * use case. + * + * If `size_out` is not null, it is set to the buffer size, or 0 if + * operation failed. + * + * The allocated buffer is aligned according to malloc which may not be + * sufficient in advanced cases - for that purpose + * `flatcc_builder_finalize_aligned_buffer` may be used. + * + * It may be worth calling `flatcc_builder_get_direct_buffer` first to see + * if the buffer is small enough to avoid copying. + * + * The returned buffer must be deallocated using `free`. + */ +void *flatcc_builder_finalize_buffer(flatcc_builder_t *B, size_t *size_out); + +/* + * Only for use with the default emitter. + * + * Similar to `flatcc_builder_finalize_buffer` but ensures the returned + * memory is aligned to the overall alignment required for the buffer. + * Often it is not necessary unless special operations rely on larger + * alignments than the stored scalars. + * + * If `size_out` is not null, it is set to the buffer size, or 0 if + * operation failed. + * + * The returned buffer must be deallocated using `aligned_free` which is + * implemented via `flatcc_flatbuffers.h`. `free` will usually work but + * is not portable to platforms without posix_memalign or C11 + * aligned_alloc support. + */ +void *flatcc_builder_finalize_aligned_buffer(flatcc_builder_t *B, size_t *size_out); + +/* + * Only for use with the default emitter. + * + * Convenience method to copy buffer from default emitter. Forwards + * call to default emitter and returns input pointer, or null if + * the emitter is not default or of the given size is smaller than + * the buffer size. + * + * Note: the `size` argument is the target buffers capacity, not the + * flatcc_builders buffer size. + * + * Other emitters have custom interfaces for reaching their content. + */ +void *flatcc_builder_copy_buffer(flatcc_builder_t *B, void *buffer, size_t size); + +#endif /* FLATCC_BUILDER_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_emitter.h b/Source/library/FBSUtil/flatcc/flatcc_emitter.h new file mode 100644 index 0000000..af9a5af --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_emitter.h @@ -0,0 +1,198 @@ +#ifndef FLATCC_EMITTER_H +#define FLATCC_EMITTER_H + +/* + * Default implementation of a flatbuilder emitter. + * + * This may be used as a starting point for more advanced emitters, + * for example writing completed pages to disk or network and + * the recycling those pages. + */ + +#include +#include + +#include "flatcc/flatcc_types.h" +#include "flatcc/flatcc_iov.h" + +/* + * The buffer steadily grows during emission but the design allows for + * an extension where individual pages can recycled before the buffer + * is complete, for example because they have been transmitted. + * + * When done, the buffer can be cleared to free all memory, or reset to + * maintain an adaptive page pool for next buffer construction. + * + * Unlike an exponentially growing buffer, each buffer page remains + * stable in memory until reset, clear or recycle is called. + * + * Design notes for possible extensions: + * + * The buffer is a ring buffer marked by a front and a back page. The + * front and back may be the same page and may initially be absent. + * Anything outside these pages are unallocated pages for recycling. + * Any page between (but excluding) the front and back pages may be + * recycled by unlinking and relinking outside the front and back pages + * but then copy operations no longer makes sense. Each page stores the + * logical offset within the buffer but isn't otherwise used by the + * implemention - it might be used for network transmission. The buffer + * is not explicitly designed for multithreaded access but any page + * strictly between front and back is not touched unless recycled and in + * this case aligned allocation is useful to prevent cache line sharing. + */ + +/* + * Memory is allocated in fixed size page units - the first page is + * split between front and back so each get half the page size. If the + * size is a multiple of 128 then each page offset will be a multiple of + * 64, which may be useful for sequencing etc. + */ +#ifndef FLATCC_EMITTER_PAGE_SIZE +#define FLATCC_EMITTER_MAX_PAGE_SIZE 3000 +#define FLATCC_EMITTER_PAGE_MULTIPLE 64 +#define FLATCC_EMITTER_PAGE_SIZE ((FLATCC_EMITTER_MAX_PAGE_SIZE) &\ + ~(2 * (FLATCC_EMITTER_PAGE_MULTIPLE) - 1)) +#endif + +#ifndef FLATCC_EMITTER_ALLOC +#ifdef FLATCC_EMITTER_USE_ALIGNED_ALLOC +/* + * does not always provide aligned_alloc, so include whatever + * is required when enabling this feature. + */ +#define FLATCC_EMITTER_ALLOC(n) aligned_alloc(FLATCC_EMITTER_PAGE_MULTIPLE,\ + (((n) + FLATCC_EMITTER_PAGE_MULTIPLE - 1) & ~(FLATCC_EMITTER_PAGE_MULTIPLE - 1))) +#else +#define FLATCC_EMITTER_ALLOC malloc +#endif +#endif + +typedef struct flatcc_emitter_page flatcc_emitter_page_t; +typedef struct flatcc_emitter flatcc_emitter_t; + +struct flatcc_emitter_page { + uint8_t page[FLATCC_EMITTER_PAGE_SIZE]; + flatcc_emitter_page_t *next; + flatcc_emitter_page_t *prev; + /* + * The offset is relative to page start, but not necessarily + * to any present content if part of front or back page, + * and undefined for unused pages. + */ + flatbuffers_soffset_t page_offset; +}; + +/* + * Must be allocated and zeroed externally, e.g. on the stack + * then provided as emit_context to the flatbuilder along + * with the `flatcc_emitter` function. + */ +struct flatcc_emitter { + flatcc_emitter_page_t *front, *back; + uint8_t *front_cursor; + size_t front_left; + uint8_t *back_cursor; + size_t back_left; + size_t used; + size_t capacity; + size_t used_average; +}; + +/* Optional helper to ensure emitter is zeroed initially. */ +static inline void flatcc_emitter_init(flatcc_emitter_t *E) +{ + memset(E, 0, sizeof(*E)); +} + +/* Deallocates all buffer memory making the emitter ready for next use. */ +void flatcc_emitter_clear(flatcc_emitter_t *E); + +/* + * Similar to `clear_flatcc_emitter` but heuristacally keeps some allocated + * memory between uses while gradually reducing peak allocations. + * For small buffers, a single page will remain available with no + * additional allocations or deallocations after first use. + */ +void flatcc_emitter_reset(flatcc_emitter_t *E); + +/* + * Helper function that allows a page between front and back to be + * recycled while the buffer is still being constructed - most likely as part + * of partial copy or transmission. Attempting to recycle front or back + * pages will result will result in an error. Recycling pages outside the + * front and back will be valid but pointless. After recycling and copy + * operations are no longer well-defined and should be replaced with + * whatever logic is recycling the pages. The reset operation + * automatically recycles all (remaining) pages when emission is + * complete. After recycling, the `flatcc_emitter_size` function will + * return as if recycle was not called, but will only represent the + * logical size, not the size of the active buffer. Because a recycled + * page is fully utilized, it is fairly easy to compensate for this if + * required. + * + * Returns 0 on success. + */ +int flatcc_emitter_recycle_page(flatcc_emitter_t *E, flatcc_emitter_page_t *p); + +/* + * The amount of data copied with `flatcc_emitter_copy_buffer` and related + * functions. Normally called at end of buffer construction but is + * always valid, as is the copy functions. The size is a direct + * function of the amount emitted data so the flatbuilder itself can + * also provide this information. + */ +static inline size_t flatcc_emitter_get_buffer_size(flatcc_emitter_t *E) +{ + return E->used; +} + +/* + * Returns buffer start iff the buffer fits on a single internal page. + * Only useful for fairly small buffers - about half the page size since + * one half of first page goes to vtables that likely use little space. + * Returns null if request could not be served. + * + * If `size_out` is not null, it is set to the buffer size, or 0 if + * operation failed. + */ +static inline void *flatcc_emitter_get_direct_buffer(flatcc_emitter_t *E, size_t *size_out) +{ + if (E->front == E->back) { + if (size_out) { + *size_out = E->used; + } + return E->front_cursor; + } + if (size_out) { + *size_out = 0; + } + return 0; +} + +/* + * Copies the internal flatcc_emitter representation to an externally + * provided linear buffer that must have size `flatcc_emitter_get_size`. + * + * If pages have been recycled, only the remaining pages will be copied + * and thus less data than what `flatcc_emitter_get_size` would suggest. It + * makes more sense to provide a customized copy operation when + * recycling pages. + * + * If the buffer is too small, nothing is copied, otherwise the + * full buffer is copied and the input buffer is returned. + */ +void *flatcc_emitter_copy_buffer(flatcc_emitter_t *E, void *buf, size_t size); + +/* + * The emitter interface function to the flatbuilder API. + * `emit_context` should be of type `flatcc_emitter_t` for this + * particular implementation. + * + * This function is compatible with the `flatbuilder_emit_fun` + * type defined in "flatbuilder.h". + */ +int flatcc_emitter(void *emit_context, + const flatcc_iovec_t *iov, int iov_count, + flatbuffers_soffset_t offset, size_t len); + +#endif /* FLATCC_EMITTER_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_endian.h b/Source/library/FBSUtil/flatcc/flatcc_endian.h new file mode 100644 index 0000000..ffef503 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_endian.h @@ -0,0 +1,115 @@ +#ifndef FLATCC_ENDIAN_H +#define FLATCC_ENDIAN_H + +/* + * This file provides helper macros to define type-specific macros and + * inline functions that convert between stored data and native data + * indedpently of both native (host) endianness and protocol endianness + * (i.e. the serialized endian format). + * + * To detect endianness correctly ensure one of the following is defined. + * + * __LITTLE_ENDIAN__ + * __BIG_ENDIAN__ + * FLATBUFFERS_LITTLEENDIAN=1 + * FLATBUFFERS_LITTLEENDIAN=0 + * + * Note: the Clang compiler likely already does this, but other + * compilers may have their own way, if at all. + * + * It is also necessary to include or a compatible + * implementation in order to provide: + * + * le16toh, le32to, le64toh, be16toh, be32toh, be64toh, + * htole16, htole32, htole64, htobe16, htobe32, htobe64. + * + * A simple way to ensure all of the above for most platforms is + * to include the portable endian support file: + * + * #include "flatcc/portable/pendian.h" + * + * It is also necessary to include + * + * #include "flatcc/flatcc_types.h" + * + * or an equivalent file. This makes it possible to change the + * endianness of the serialized data and the sizes of flatbuffer + * specific types such as `uoffset_t`. + * + * Note: the mentioned include files are likely already included + * by the file including this file, at least for the default + * configuration. + */ + +#ifndef UINT8_t +#include +#endif + +/* These are needed to simplify accessor macros and are not found in . */ +#ifndef le8toh +#define le8toh(n) (n) +#endif + +#ifndef be8toh +#define be8toh(n) (n) +#endif + +#ifndef htole8 +#define htole8(n) (n) +#endif + +#ifndef htobe8 +#define htobe8(n) (n) +#endif + +#include "flatcc/flatcc_accessors.h" + +/* This is the binary encoding endianness, usually LE for flatbuffers. */ +#if FLATBUFFERS_PROTOCOL_IS_LE +#define flatbuffers_endian le +#elif FLATBUFFERS_PROTOCOL_IS_BE +#define flatbuffers_endian be +#else +#error "flatbuffers has no defined endiannesss" +#endif + + __flatcc_define_basic_scalar_accessors(flatbuffers_, flatbuffers_endian) + + __flatcc_define_integer_accessors(flatbuffers_bool, flatbuffers_bool_t, + FLATBUFFERS_BOOL_WIDTH, flatbuffers_endian) + + __flatcc_define_integer_accessors(__flatbuffers_uoffset, flatbuffers_uoffset_t, + FLATBUFFERS_UOFFSET_WIDTH, flatbuffers_endian) + __flatcc_define_integer_accessors(__flatbuffers_soffset, flatbuffers_soffset_t, + FLATBUFFERS_SOFFSET_WIDTH, flatbuffers_endian) + __flatcc_define_integer_accessors(__flatbuffers_voffset, flatbuffers_voffset_t, + FLATBUFFERS_VOFFSET_WIDTH, flatbuffers_endian) + __flatcc_define_integer_accessors(__flatbuffers_utype, flatbuffers_utype_t, + FLATBUFFERS_UTYPE_WIDTH, flatbuffers_endian) + __flatcc_define_integer_accessors(__flatbuffers_thash, flatbuffers_thash_t, + FLATBUFFERS_THASH_WIDTH, flatbuffers_endian) + +/* flatcc/portable/pendian.h sets LITTLE/BIG flags if possible, and always defines le16toh. */ +#ifndef flatbuffers_is_native_pe +#if defined(__LITTLE_ENDIAN__) || FLATBUFFERS_LITTLEENDIAN +#undef FLATBUFFERS_LITTLEENDIAN +#define FLATBUFFERS_LITTLEENDIAN 1 +#define flatbuffers_is_native_pe() (FLATBUFFERS_PROTOCOL_IS_LE) +#elif defined(__BIG_ENDIAN__) || (defined(FLATBUFFERS_LITTLEENDIAN) && !FLATBUFFERS_LITTLEENDIAN) +#undef FLATBUFFERS_LITTLEENDIAN +#define FLATBUFFERS_LITTLEENDIAN 0 +#define flatbuffers_is_native_pe() (FLATBUFFERS_PROTOCOL_IS_BE) +#else +#define flatbuffers_is_native_pe() (__FLATBUFFERS_CONCAT(flatbuffers_endian, 16toh)(1) == 1) +#endif +#endif + +#ifndef flatbuffers_is_native_le +#define flatbuffers_is_native_le() flatbuffers_is_native_pe() +#endif + +#ifndef flatbuffers_is_native_be +#define flatbuffers_is_native_be() (!flatbuffers_is_native_pe()) +#endif + +#endif /* FLATCC_ENDIAN_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_flatbuffers.h b/Source/library/FBSUtil/flatcc/flatcc_flatbuffers.h new file mode 100644 index 0000000..071ad89 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_flatbuffers.h @@ -0,0 +1,48 @@ +/* + * Even C11 compilers depend on clib support for `static_assert` which + * isn't always present, so we deal with this here for all compilers. + * + * Outside include guard to handle scope counter. + */ +#include "flatcc/portable/pstatic_assert.h" + +#ifndef FLATCC_FLATBUFFERS_H +#define FLATCC_FLATBUFFERS_H + +#ifndef flatcc_flatbuffers_defined +#define flatcc_flatbuffers_defined + +#ifdef FLATCC_PORTABLE +#include "flatcc/flatcc_portable.h" +#endif +#include "flatcc/portable/pwarnings.h" +/* Needed by C99 compilers without FLATCC_PORTABLE. */ +#include "flatcc/portable/pstdalign.h" + +/* + * Implements `aligned_alloc` and `aligned_free`. + * Even with C11, this implements non-standard aligned_free needed for portable + * aligned_alloc implementations. + */ +#include "flatcc/portable/paligned_alloc.h" + +#define __FLATBUFFERS_PASTE2(a, b) a ## b +#define __FLATBUFFERS_PASTE3(a, b, c) a ## b ## c +#define __FLATBUFFERS_CONCAT(a, b) __FLATBUFFERS_PASTE2(a, b) + +/* + * "flatcc_endian.h" requires the preceeding include files, + * or compatible definitions. + */ +#include "flatcc/portable/pendian.h" +#include "flatcc/flatcc_types.h" +#include "flatcc/flatcc_endian.h" +#include "flatcc/flatcc_identifier.h" + +#ifndef FLATBUFFERS_WRAP_NAMESPACE +#define FLATBUFFERS_WRAP_NAMESPACE(ns, x) ns ## _ ## x +#endif + +#endif /* flatcc_flatbuffers_defined */ + +#endif /* FLATCC_FLATBUFFERS_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_identifier.h b/Source/library/FBSUtil/flatcc/flatcc_identifier.h new file mode 100644 index 0000000..fcceb34 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_identifier.h @@ -0,0 +1,115 @@ +#ifndef FLATCC_IDENTIFIER_H +#define FLATCC_IDENTIFIER_H + +#ifndef FLATCC_FLATBUFFERS_H +#error "include via flatcc/flatcc_flatbuffers.h" +#endif + +/* + * FlatBuffers identifiers are normally specified by "file_identifer" in + * the schema, but a standard hash of the fully qualified type name can + * also be used. This file implements such a mapping, but the generated + * headers also contain the necessary information for known types. + */ + + +/* + * Returns the type hash of a given name in native endian format. + * Generated code already provides these, but if a name was changed + * in the schema it may be relevant to recompute the hash manually. + * + * The wire-format of this value should always be little endian. + * + * Note: this must be the fully qualified name, e.g. in the namespace + * "MyGame.Example": + * + * flatbuffers_type_hash_from_name("MyGame.Example.Monster"); + * + * or, in the global namespace just: + * + * flatbuffers_type_hash_from_name("MyTable"); + * + * This assumes 32 bit hash type. For other sizes, other FNV-1a + * constants would be required. + * + * Note that we reserve hash value 0 for missing or ignored value. + */ +static inline flatbuffers_thash_t flatbuffers_type_hash_from_name(const char *name) +{ + uint32_t hash = 2166136261UL; + while (*name) { + hash ^= (uint32_t)*name; + hash = hash * 16777619UL; + ++name; + } + if (hash == 0) { + hash = 2166136261UL; + } + return hash; +} + +/* + * Type hash encoded as little endian file identifier string. + * Note: if type hash is 0, the identifier should be null which + * we cannot return in this interface. + */ +static inline void flatbuffers_identifier_from_type_hash(flatbuffers_thash_t type_hash, flatbuffers_fid_t out_identifier) +{ + out_identifier[0] = type_hash & 0xff; + type_hash >>= 8; + out_identifier[1] = type_hash & 0xff; + type_hash >>= 8; + out_identifier[2] = type_hash & 0xff; + type_hash >>= 8; + out_identifier[3] = type_hash & 0xff; +} + +/* Native integer encoding of file identifier. */ +static inline flatbuffers_thash_t flatbuffers_type_hash_from_identifier(const flatbuffers_fid_t identifier) +{ + uint8_t *p = (uint8_t *)identifier; + + return identifier ? + (uint32_t)p[0] + (((uint32_t)p[1]) << 8) + (((uint32_t)p[2]) << 16) + (((uint32_t)p[3]) << 24) : 0; +} + +/* + * Computes the little endian wire format of the type hash. It can be + * used as a file identifer argument to various flatcc buffer calls. + * + * `flatbuffers_fid_t` is just `char [4]` for the default flatbuffers + * type system defined in `flatcc/flatcc_types.h`. + */ +static inline void flatbuffers_identifier_from_name(const char *name, flatbuffers_fid_t out_identifier) +{ + flatbuffers_identifier_from_type_hash(flatbuffers_type_hash_from_name(name), out_identifier); +} + +/* + * This is a collision free hash (a permutation) of the type hash to + * provide better distribution for use in hash tables. It is likely not + * necessary in praxis, and for uniqueness of identifiers it provides no + * advantage over just using the FNV-1a type hash, except when truncating + * the identifier to less than 32-bits. + * + * Note: the output should not be used in transmission. It provides no + * additional information and just complicates matters. Furthermore, the + * unmodified type hash has the benefit that it can seed a child namespace. + */ +static inline uint32_t flatbuffers_disperse_type_hash(flatbuffers_thash_t type_hash) +{ + /* http://stackoverflow.com/a/12996028 */ + uint32_t x = type_hash; + + x = ((x >> 16) ^ x) * 0x45d9f3bUL; + x = ((x >> 16) ^ x) * 0x45d9f3bUL; + x = ((x >> 16) ^ x); + return x; +} + + +/* We have hardcoded assumptions about identifier size. */ +static_assert(sizeof(flatbuffers_fid_t) == 4, "unexpected file identifier size"); +static_assert(sizeof(flatbuffers_thash_t) == 4, "unexpected type hash size"); + +#endif /* FLATCC_IDENTIFIER_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_iov.h b/Source/library/FBSUtil/flatcc/flatcc_iov.h new file mode 100644 index 0000000..858e2de --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_iov.h @@ -0,0 +1,23 @@ +#ifndef FLATCC_IOV_H +#define FLATCC_IOV_H + +#include + +/* + * The emitter receives one, or a few buffers at a time via + * this type. compatible iovec structure used for + * allocation and emitter interface. + */ +typedef struct flatcc_iovec flatcc_iovec_t; +struct flatcc_iovec { + void *iov_base; + size_t iov_len; +}; + +/* + * The largest iovec vector the builder will issue. It will + * always be a relatively small number. + */ +#define FLATCC_IOV_COUNT_MAX 8 + +#endif /* FLATCC_IOV_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_json_parser.h b/Source/library/FBSUtil/flatcc/flatcc_json_parser.h new file mode 100644 index 0000000..69b9a3c --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_json_parser.h @@ -0,0 +1,793 @@ +#ifndef FLATCC_JSON_PARSE_H +#define FLATCC_JSON_PARSE_H + +/* + * JSON RFC: + * http://www.ietf.org/rfc/rfc4627.txt?number=4627 + * + * With several flatbuffers specific extensions. + */ + +#include +#include +#include + +#include "flatcc/flatcc_rtconfig.h" +#include "flatcc/flatcc_builder.h" +#include "flatcc/flatcc_unaligned.h" + +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" + +enum flatcc_json_parser_flags { + flatcc_json_parser_f_skip_unknown = 1, + flatcc_json_parser_f_force_add = 2 +}; + +#define FLATCC_JSON_PARSE_ERROR_MAP(XX) \ + XX(ok, "ok") \ + XX(eof, "eof") \ + XX(deep_nesting, "deep nesting") \ + XX(trailing_comma, "trailing comma") \ + XX(expected_colon, "expected colon") \ + XX(unexpected_character, "unexpected character") \ + XX(invalid_numeric, "invalid numeric") \ + XX(overflow, "overflow") \ + XX(underflow, "underflow") \ + XX(unbalanced_array, "unbalanced array") \ + XX(unbalanced_object, "unbalanced object") \ + XX(precision_loss, "precision loss") \ + XX(float_unexpected, "float unexpected") \ + XX(unknown_symbol, "unknown symbol") \ + XX(unquoted_symbolic_list, "unquoted list of symbols") \ + XX(unknown_union, "unknown union type") \ + XX(expected_string, "expected string") \ + XX(invalid_character, "invalid character") \ + XX(invalid_escape, "invalid escape") \ + XX(invalid_type, "invalid type") \ + XX(unterminated_string, "unterminated string") \ + XX(expected_object, "expected object") \ + XX(expected_array, "expected array") \ + XX(expected_scalar, "expected literal or symbolic scalar") \ + XX(expected_union_type, "expected union type") \ + XX(union_none, "union is NONE") \ + XX(union_incomplete, "table has incomplete union") \ + XX(duplicate, "table has duplicate field") \ + XX(required, "required field missing") \ + XX(runtime, "runtime error") \ + XX(not_supported, "not supported") + +enum flatcc_json_parser_error_no { +#define XX(no, str) flatcc_json_parser_error_##no, + FLATCC_JSON_PARSE_ERROR_MAP(XX) +#undef XX +}; + +const char *flatcc_json_parser_error_string(int err); + +#define flatcc_json_parser_ok flatcc_json_parser_error_ok +#define flatcc_json_parser_eof flatcc_json_parser_error_eof + +/* + * The struct may be zero initialized in which case the line count will + * start at line zero, or the line may be set to 1 initially. The ctx + * is only used for error reporting and tracking non-standard unquoted + * ctx. + * + * `ctx` may for example hold a flatcc_builder_t pointer. + */ +typedef struct flatcc_json_parser_ctx flatcc_json_parser_t; +struct flatcc_json_parser_ctx { + flatcc_builder_t *ctx; + const char *line_start; + int flags; +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + int unquoted; +#endif + + int line, pos; + int error; + const char *start; + const char *end; + const char *error_loc; + /* Set at end of successful parse. */ + const char *end_loc; +}; + +static inline int flatcc_json_parser_get_error(flatcc_json_parser_t *ctx) +{ + return ctx->error; +} + +static inline void flatcc_json_parser_init(flatcc_json_parser_t *ctx, flatcc_builder_t *B, const char *buf, const char *end, int flags) +{ + memset(ctx, 0, sizeof(*ctx)); + ctx->ctx = B; + ctx->line_start = buf; + ctx->line = 1; + ctx->flags = flags; + /* These are not needed for parsing, but may be helpful in reporting etc. */ + ctx->start = buf; + ctx->end = end; + ctx->error_loc = buf; +} + +const char *flatcc_json_parser_set_error(flatcc_json_parser_t *ctx, const char *loc, const char *end, int reason); + +/* + * Wide space is not necessarily beneficial in the typical space, but it + * also isn't expensive so it may be added when there are applications + * that can benefit. + */ +const char *flatcc_json_parser_space_ext(flatcc_json_parser_t *ctx, const char *buf, const char *end); + +static inline const char *flatcc_json_parser_space(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + if (end - buf > 1) { + if (buf[0] > 0x20) { + return buf; + } + if (buf[0] == 0x20 && buf[1] > 0x20) { + return buf + 1; + } + } + return flatcc_json_parser_space_ext(ctx, buf, end); +} + + +static inline const char *flatcc_json_parser_string_start(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + if (buf == end || *buf != '\"') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_expected_string); + } + return ++buf; +} + +static inline const char *flatcc_json_parser_string_end(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + if (buf == end || *buf != '\"') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unterminated_string); + } + return ++buf; +} + +/* + * Creates a string. Returns *ref == 0 on unrecoverable error or + * sets *ref to a valid new string reference. + */ +const char *flatcc_json_parser_build_string(flatcc_json_parser_t *ctx, + const char *buf, const char *end, flatcc_builder_ref_t *ref); + +typedef char flatcc_json_parser_escape_buffer_t[4]; +/* + * If the buffer does not hold a valid escape sequence, an error is + * returned with code[0] = 0/ + * + * Otherwise code[0] the length (1-3) of the remaining + * characters in the code, transcoded from the escape sequence. + * + * The JSON extension `\xXX` is supported and may produced invalid UTF-8 + * characters such as 0xff. The standard JSON escape `\uXXXX` is not + * checked for invalid code points and may produce invlalid UTF-8. + * + * Regular characters are expected to valid UTF-8 but they are not checked + * and may therefore produce invalid UTF-8. + * + * Control characters within a string are rejected except in the + * standard JSON escpaped form for `\n \r \t \b \f`. + * + * Additional escape codes as per standard JSON: `\\ \/ \"`. + */ +const char *flatcc_json_parser_string_escape(flatcc_json_parser_t *ctx, const char *buf, const char *end, flatcc_json_parser_escape_buffer_t code); + +/* + * Parses the longest unescaped run of string content followed by either + * an escape encoding, string termination, or error. + */ +const char *flatcc_json_parser_string_part(flatcc_json_parser_t *ctx, const char *buf, const char *end); + +static inline const char *flatcc_json_parser_symbol_start(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + if (buf == end) { + return buf; + } + if (*buf == '\"') { + ++buf; +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + ctx->unquoted = 0; +#endif + } else { +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + if (*buf == '.') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unexpected_character); + } + ctx->unquoted = 1; +#else + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unexpected_character); +#endif + } + return buf; +} + +static inline uint64_t flatcc_json_parser_symbol_part_ext(const char *buf, const char *end) +{ + uint64_t w = 0; + size_t n = end - buf; + + if (n > 8) { + n = 8; + } + /* This can bloat inlining for a rarely executed case. */ +#if 1 + switch (n) { + case 8: w |= ((uint64_t)buf[7]) << (0 * 8); + case 7: w |= ((uint64_t)buf[6]) << (1 * 8); + case 6: w |= ((uint64_t)buf[5]) << (2 * 8); + case 5: w |= ((uint64_t)buf[4]) << (3 * 8); + case 4: w |= ((uint64_t)buf[3]) << (4 * 8); + case 3: w |= ((uint64_t)buf[2]) << (5 * 8); + case 2: w |= ((uint64_t)buf[1]) << (6 * 8); + case 1: w |= ((uint64_t)buf[0]) << (7 * 8); + case 0: + break; + } +#else + /* But this is hardly much of an improvement. */ + { + size_t i; + for (i = 0; i < n; ++i) { + w <<= 8; + if (i < n) { + w = buf[i]; + } + } + } +#endif + return w; +} + +/* + * Read out string as a big endian word. This allows for trie lookup, + * also when trailing characters are beyond keyword. This assumes the + * external words tested against are valid and therefore there need be + * no checks here. If a match is not made, the symbol_end function will + * consume and check any unmatched content - from _before_ this function + * was called - i.e. the returned buffer is tentative for use only if we + * accept the part returned here. + * + * Used for both symbols and symbolic constants. + */ +static inline uint64_t flatcc_json_parser_symbol_part(const char *buf, const char *end) +{ + size_t n = end - buf; + +#if FLATCC_ALLOW_UNALIGNED_ACCESS + if (n >= 8) { + return be64toh(*(uint64_t *)buf); + } +#endif + return flatcc_json_parser_symbol_part_ext(buf, end); +} + +/* Don't allow space in dot notation neither inside nor outside strings. */ +static inline const char *flatcc_json_parser_match_scope(flatcc_json_parser_t *ctx, const char *buf, const char *end, int pos) +{ + const char *mark = buf; + + (void)ctx; + + if (end - buf <= pos) { + return mark; + } + if (buf[pos] != '.') { + return mark; + } + return buf + pos + 1; +} + +const char *flatcc_json_parser_match_constant(flatcc_json_parser_t *ctx, const char *buf, const char *end, int pos, int *more); + +/* We allow '.' in unquoted symbols, but not at the start or end. */ +static inline const char *flatcc_json_parser_symbol_end(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + char c, clast = 0; + + +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + if (ctx->unquoted) { + while (buf != end && *buf > 0x20) { + clast = c = *buf; + if (c == '_' || c == '.' || (c & 0x80) || (c >= '0' && c <= '9')) { + ++buf; + continue; + } + /* Lower case. */ + c |= 0x20; + if (c >= 'a' && c <= 'z') { + ++buf; + continue; + } + break; + } + if (clast == '.') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unexpected_character); + } + } else { +#else + { +#endif + while (buf != end && *buf != '\"') { + if (*buf == '\\') { + if (end - buf < 2) { + break; + } + ++buf; + } + ++buf; + } + if (buf == end || *buf != '\"') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unterminated_string); + } + ++buf; + } + return buf; +} + +static inline const char *flatcc_json_parser_constant_start(flatcc_json_parser_t *ctx, const char *buf, const char *end) +{ + buf = flatcc_json_parser_symbol_start(ctx, buf, end); +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + if (!ctx->unquoted) { +#else + { +#endif + buf = flatcc_json_parser_space(ctx, buf, end); + } + return buf; +} + +static inline const char *flatcc_json_parser_object_start(flatcc_json_parser_t *ctx, const char *buf, const char *end, int *more) +{ + if (buf == end || *buf != '{') { + *more = 0; + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_expected_object); + } + buf = flatcc_json_parser_space(ctx, buf + 1, end); + if (buf != end && *buf == '}') { + *more = 0; + return flatcc_json_parser_space(ctx, buf + 1, end); + } + *more = 1; + return buf; +} + +static inline const char *flatcc_json_parser_object_end(flatcc_json_parser_t *ctx, const char *buf, + const char *end, int *more) +{ + buf = flatcc_json_parser_space(ctx, buf, end); + if (buf == end) { + *more = 0; + return buf; + } + if (*buf != ',') { + *more = 0; + if (*buf != '}') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unbalanced_object); + } else { + return flatcc_json_parser_space(ctx, buf + 1, end); + } + } + buf = flatcc_json_parser_space(ctx, buf + 1, end); + if (buf == end) { + *more = 0; + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unbalanced_object); + } +#if FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA + if (*buf == '}') { + *more = 0; + return flatcc_json_parser_space(ctx, buf + 1, end); + } +#endif + *more = 1; + return buf; +} + +static inline const char *flatcc_json_parser_array_start(flatcc_json_parser_t *ctx, const char *buf, const char *end, int *more) +{ + if (buf == end || *buf != '[') { + *more = 0; + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_expected_array); + } + buf = flatcc_json_parser_space(ctx, buf + 1, end); + if (buf != end && *buf == ']') { + *more = 0; + return flatcc_json_parser_space(ctx, buf + 1, end); + } + *more = 1; + return buf; +} + +static inline const char *flatcc_json_parser_array_end(flatcc_json_parser_t *ctx, const char *buf, + const char *end, int *more) +{ + buf = flatcc_json_parser_space(ctx, buf, end); + if (buf == end) { + *more = 0; + return buf; + } + if (*buf != ',') { + *more = 0; + if (*buf != ']') { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unbalanced_array); + } else { + return flatcc_json_parser_space(ctx, buf + 1, end); + } + } + buf = flatcc_json_parser_space(ctx, buf + 1, end); + if (buf == end) { + *more = 0; + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_unbalanced_array); + } +#if FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA + if (*buf == ']') { + *more = 0; + return flatcc_json_parser_space(ctx, buf + 1, end); + } +#endif + *more = 1; + return buf; +} + +/* + * Detects if a symbol terminates at a given `pos` relative to the + * buffer pointer, or return fast. + * + * Failure to match is not an error but a recommendation to try + * alternative longer suffixes - only if such do not exist will + * there be an error. If a match was not eventually found, + * the `flatcc_json_parser_unmatched_symbol` should be called to consume + * the symbol and generate error messages. + * + * If a match was detected, ':' and surrounding space is consumed, + * or an error is generated. + */ +static inline const char *flatcc_json_parser_match_symbol(flatcc_json_parser_t *ctx, const char *buf, + const char *end, int pos) +{ + const char *mark = buf; + + if (end - buf <= pos) { + return mark; + } +#if FLATCC_JSON_PARSE_ALLOW_UNQUOTED + if (ctx->unquoted) { + if (buf[pos] > 0x20 && buf[pos] != ':') { + return mark; + } + buf += pos; + ctx->unquoted = 0; + } else { +#else + { +#endif + if (buf[pos] != '\"') { + return mark; + } + buf += pos + 1; + } + buf = flatcc_json_parser_space(ctx, buf, end); + if (buf != end && *buf == ':') { + ++buf; + return flatcc_json_parser_space(ctx, buf, end); + } + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_expected_colon); +} + +static inline const char *flatcc_json_parser_match_type_suffix(flatcc_json_parser_t *ctx, const char *buf, const char *end, int pos) +{ + if (end - buf <= pos + 5) { + return buf; + } + if (memcmp(buf + pos, "_type", 5)) { + return buf; + } + return flatcc_json_parser_match_symbol(ctx, buf, end, pos + 5); +} + +const char *flatcc_json_parser_unmatched_symbol(flatcc_json_parser_t *ctx, const char *buf, const char *end); + +static inline const char *flatcc_json_parser_coerce_uint64( + flatcc_json_parser_t *ctx, const char *buf, + const char *end, int value_sign, uint64_t value, uint64_t *v) +{ + if (value_sign) { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_underflow); + } + *v = value; + return buf; +} + +static inline const char *flatcc_json_parser_coerce_bool(flatcc_json_parser_t *ctx, const char *buf, + const char *end, int value_sign, uint64_t value, uint8_t *v) +{ + if (value_sign) { + return flatcc_json_parser_set_error(ctx, buf, end, flatcc_json_parser_error_underflow); + } + *v = (uint8_t)!!value; + return buf; +} + +#define __flatcc_json_parser_define_coerce_unsigned(type, basetype, uctype) \ +static inline const char *flatcc_json_parser_coerce_ ## type( \ + flatcc_json_parser_t *ctx, const char *buf, \ + const char *end, int value_sign, uint64_t value, basetype *v) \ +{ \ + if (value_sign) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_underflow); \ + } \ + if (value > uctype ## _MAX) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_overflow); \ + } \ + *v = (basetype)value; \ + return buf; \ +} + +__flatcc_json_parser_define_coerce_unsigned(uint32, uint32_t, UINT32) +__flatcc_json_parser_define_coerce_unsigned(uint16, uint16_t, UINT16) +__flatcc_json_parser_define_coerce_unsigned(uint8, uint8_t, UINT8) + +#define __flatcc_json_parser_define_coerce_signed(type, basetype, uctype) \ +static inline const char *flatcc_json_parser_coerce_ ## type( \ + flatcc_json_parser_t *ctx, const char *buf, \ + const char *end, int value_sign, uint64_t value, basetype *v) \ +{ \ + if (value_sign) { \ + if (value > (uint64_t)(uctype ## _MAX) + 1) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_underflow); \ + } \ + *v = (basetype)-(int64_t)value; \ + } else { \ + if (value > uctype ## _MAX) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_overflow); \ + } \ + *v = (basetype)value; \ + } \ + return buf; \ +} + +__flatcc_json_parser_define_coerce_signed(int64, int64_t, INT64) +__flatcc_json_parser_define_coerce_signed(int32, int32_t, INT32) +__flatcc_json_parser_define_coerce_signed(int16, int16_t, INT16) +__flatcc_json_parser_define_coerce_signed(int8, int8_t, INT8) + +static inline const char *flatcc_json_parser_coerce_float( + flatcc_json_parser_t *ctx, const char *buf, + const char *end, int value_sign, uint64_t value, float *v) +{ + (void)ctx; + (void)end; + + *v = value_sign ? -(float)value : (float)value; + return buf; +} + +static inline const char *flatcc_json_parser_coerce_double( + flatcc_json_parser_t *ctx, const char *buf, + const char *end, int value_sign, uint64_t value, double *v) +{ + (void)ctx; + (void)end; + + *v = value_sign ? -(double)value : (double)value; + return buf; +} + +const char *flatcc_json_parser_double(flatcc_json_parser_t *ctx, const char *buf, const char *end, double *v); + +const char *flatcc_json_parser_float(flatcc_json_parser_t *ctx, const char *buf, const char *end, float *v); + +/* + * If the buffer does not contain a valid start character for a numeric + * value, the function will return the the input buffer without failure. + * This makes is possible to try a symbolic parse. + */ +const char *flatcc_json_parser_integer(flatcc_json_parser_t *ctx, const char *buf, const char *end, + int *value_sign, uint64_t *value); + +/* Returns unchanged buffer without error if `null` is not matched. */ +static inline const char *flatcc_json_parser_null(const char *buf, const char *end) +{ + if (end - buf >= 4 && memcmp(buf, "null", 4) == 0) { + return buf + 4; + } + return buf; +} + +/* + * `parsers` is a null terminated array of parsers with at least one + * valid parser. A numeric literal parser may also be included. + */ +#define __flatcc_json_parser_define_integral_parser(type, basetype) \ +static inline const char *flatcc_json_parser_ ## type( \ + flatcc_json_parser_t *ctx, \ + const char *buf, const char *end, basetype *v) \ +{ \ + uint64_t value = 0; \ + int value_sign = 0; \ + const char *mark = buf; \ + \ + *v = 0; \ + if (buf == end) { \ + return buf; \ + } \ + buf = flatcc_json_parser_integer(ctx, buf, end, &value_sign, &value); \ + if (buf != mark) { \ + return flatcc_json_parser_coerce_ ## type(ctx, \ + buf, end, value_sign, value, v); \ + } \ + return buf; \ +} + +__flatcc_json_parser_define_integral_parser(uint64, uint64_t) +__flatcc_json_parser_define_integral_parser(uint32, uint32_t) +__flatcc_json_parser_define_integral_parser(uint16, uint16_t) +__flatcc_json_parser_define_integral_parser(uint8, uint8_t) +__flatcc_json_parser_define_integral_parser(int64, int64_t) +__flatcc_json_parser_define_integral_parser(int32, int32_t) +__flatcc_json_parser_define_integral_parser(int16, int16_t) +__flatcc_json_parser_define_integral_parser(int8, int8_t) + +static inline const char *flatcc_json_parser_bool(flatcc_json_parser_t *ctx, const char *buf, const char *end, uint8_t *v) +{ + const char *k; + uint8_t tmp; + + k = buf; + if (end - buf >= 4 && memcmp(buf, "true", 4) == 0) { + *v = 1; + return k + 4; + } else if (end - buf >= 5 && memcmp(buf, "false", 5) == 0) { + *v = 0; + return k + 5; + } + buf = flatcc_json_parser_uint8(ctx, buf, end, &tmp); + *v = !!tmp; + return buf; +} + +/* + * The `parsers` argument is a zero terminated array of parser + * functions with increasingly general scopes. + * + * Symbols can be be or'ed together by listing multiple space separated + * flags in source being parsed, like `{ x : "Red Blue" }`. + * Intended for flags, but generally available. + * + * `aggregate` means there are more symbols to follow. + * + * This function does not return input `buf` value if match was + * unsuccessful. It will either match or error. + */ +typedef const char *flatcc_json_parser_integral_symbol_f(flatcc_json_parser_t *ctx, + const char *buf, const char *end, int *value_sign, uint64_t *value, int *aggregate); + +/* + * Raise an error if a syntax like `color: Red Green` is seen unless + * explicitly permitted. `color: "Red Green"` or `"color": "Red Green" + * or `color: Red` is permitted if unquoted is permitted but not + * unquoted list. Googles flatc JSON parser does not allow multiple + * symbolic values unless quoted, so this is the default. + */ +#if !FLATCC_JSON_PARSE_ALLOW_UNQUOTED || FLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST +#define __flatcc_json_parser_init_check_unquoted_list() +#define __flatcc_json_parser_check_unquoted_list() +#else +#define __flatcc_json_parser_init_check_unquoted_list() int list_count = 0; +#define __flatcc_json_parser_check_unquoted_list() \ + if (list_count++ && ctx->unquoted) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_unquoted_symbolic_list); \ + } +#endif + +#define __flatcc_json_parser_define_symbolic_integral_parser(type, basetype)\ +static const char *flatcc_json_parser_symbolic_ ## type( \ + flatcc_json_parser_t *ctx, \ + const char *buf, const char *end, \ + flatcc_json_parser_integral_symbol_f *parsers[], \ + basetype *v) \ +{ \ + flatcc_json_parser_integral_symbol_f **p; \ + const char *mark; \ + basetype tmp = 0; \ + uint64_t value; \ + int value_sign, aggregate; \ + __flatcc_json_parser_init_check_unquoted_list() \ + \ + *v = 0; \ + buf = flatcc_json_parser_constant_start(ctx, buf, end); \ + if (buf == end) { \ + return buf; \ + } \ + do { \ + p = parsers; \ + do { \ + /* call parser function */ \ + buf = (*p)(ctx, (mark = buf), end, \ + &value_sign, &value, &aggregate); \ + if (buf == end) { \ + return buf; \ + } \ + } while (buf == mark && *++p); \ + if (mark == buf) { \ + return flatcc_json_parser_set_error(ctx, buf, end, \ + flatcc_json_parser_error_expected_scalar); \ + } \ + __flatcc_json_parser_check_unquoted_list() \ + if (end == flatcc_json_parser_coerce_ ## type(ctx, \ + buf, end, value_sign, value, &tmp)) { \ + return end; \ + } \ + /* \ + * `+=`, not `|=` because we also coerce to float and double, \ + * and because we need to handle signed values. This may give \ + * unexpected results with duplicate flags. \ + */ \ + *v += tmp; \ + } while (aggregate); \ + return buf; \ +} + +__flatcc_json_parser_define_symbolic_integral_parser(uint64, uint64_t) +__flatcc_json_parser_define_symbolic_integral_parser(uint32, uint32_t) +__flatcc_json_parser_define_symbolic_integral_parser(uint16, uint16_t) +__flatcc_json_parser_define_symbolic_integral_parser(uint8, uint8_t) +__flatcc_json_parser_define_symbolic_integral_parser(int64, int64_t) +__flatcc_json_parser_define_symbolic_integral_parser(int32, int32_t) +__flatcc_json_parser_define_symbolic_integral_parser(int16, int16_t) +__flatcc_json_parser_define_symbolic_integral_parser(int8, int8_t) + +__flatcc_json_parser_define_symbolic_integral_parser(bool, uint8_t) + +/* We still parse integral values, but coerce to float or double. */ +__flatcc_json_parser_define_symbolic_integral_parser(float, float) +__flatcc_json_parser_define_symbolic_integral_parser(double, double) + +/* + * This doesn't do anything other than validate and advance past + * a JSON value which may use unquoted symbols. + * + * Upon call it is assumed that leading space has been stripped and that + * a JSON value is expected (i.e. root, or just after ':' in a + * container object, or less likely as an array member). Any trailing + * comma is assumed to belong to the parent context. Returns a parse + * location stripped from space so container should post call expect + * ',', '}', or ']', or EOF if the JSON is valid. + */ +const char *flatcc_json_parser_generic_json(flatcc_json_parser_t *ctx, const char *buf, const char *end); + +typedef const char *flatcc_json_parser_union_f(flatcc_json_parser_t *ctx, + const char *buf, const char *end, uint8_t type, flatbuffers_voffset_t id); + +/* Called at start by table parsers with at least 1 union. */ +const char *flatcc_json_parser_prepare_unions(flatcc_json_parser_t *ctx, + const char *buf, const char *end, size_t union_total); + +const char *flatcc_json_parser_finalize_unions(flatcc_json_parser_t *ctx, + const char *buf, const char *end); + +const char *flatcc_json_parser_union(flatcc_json_parser_t *ctx, + const char *buf, const char *end, size_t union_index, + flatbuffers_voffset_t id, flatcc_json_parser_union_f *parse); + +const char *flatcc_json_parser_union_type(flatcc_json_parser_t *ctx, + const char *buf, const char *end, size_t union_index, flatbuffers_voffset_t id, + flatcc_json_parser_integral_symbol_f *type_parsers[], + flatcc_json_parser_union_f *union_parser); + +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* FLATCC_JSON_PARSE_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_json_printer.h b/Source/library/FBSUtil/flatcc/flatcc_json_printer.h new file mode 100644 index 0000000..4b12f10 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_json_printer.h @@ -0,0 +1,651 @@ +#ifndef FLATCC_JSON_PRINTER_H +#define FLATCC_JSON_PRINTER_H + +/* + * Definitions for default implementation, do not assume these are + * always valid. + */ +#define FLATCC_JSON_PRINT_FLUSH_SIZE (1024 * 16) +#define FLATCC_JSON_PRINT_RESERVE 64 +#define FLATCC_JSON_PRINT_BUFFER_SIZE (FLATCC_JSON_PRINT_FLUSH_SIZE + FLATCC_JSON_PRINT_RESERVE) + +/* Initial size that grows exponentially. */ +#define FLATCC_JSON_PRINT_DYN_BUFFER_SIZE 4096 + + +#include +#include + +#include "flatcc/flatcc_rtconfig.h" +#include "flatcc/flatcc_flatbuffers.h" + +/* -DFLATCC_PORTABLE may help if inttypes.h is missing. */ +#ifndef PRId64 +#include +#endif + +#define FLATCC_JSON_PRINT_ERROR_MAP(XX) \ + XX(ok, "ok") \ + /* \ + * When the flatbuffer is null, has too small a header, or has \ + * mismatching identifier when a match was requested. \ + */ \ + XX(bad_input, "bad input") \ + XX(deep_recursion, "deep recursion") \ + /* \ + * When the output was larger than the available fixed size buffer, \ + * or dynamic allocation could not grow the buffer sufficiently. \ + */ \ + XX(overflow, "overflow") + +enum flatcc_json_printer_error_no { +#define XX(no, str) flatcc_json_printer_error_##no, + FLATCC_JSON_PRINT_ERROR_MAP(XX) +#undef XX +}; + +#define flatcc_json_printer_ok flatcc_json_printer_error_ok + +typedef struct flatcc_json_printer_ctx flatcc_json_printer_t; + +typedef void flatcc_json_printer_flush_f(flatcc_json_printer_t *ctx, int all); + +struct flatcc_json_printer_ctx { + char *buf; + size_t size; + size_t flush_size; + size_t total; + const char *pflush; + char *p; + uint8_t own_buffer; + uint8_t indent; + uint8_t unquote; + uint8_t noenum; + uint8_t skip_default; + uint8_t force_default; + int level; + int error; + + void *fp; + flatcc_json_printer_flush_f *flush; +}; + +static inline void flatcc_json_printer_set_error(flatcc_json_printer_t *ctx, int err) +{ + if (!ctx->error) { + ctx->error = err; + } +} + +const char *flatcc_json_printer_error_string(int err); + +static inline int flatcc_json_printer_get_error(flatcc_json_printer_t *ctx) +{ + return ctx->error; +} + +/* + * Call to reuse context between parses without without + * returning buffer. If a file pointer is being used, + * it will remain open. + * + * Reset does not affect the formatting settings indentation, and + * operational flags, but does zero the indentation level. + */ +static inline void flatcc_json_printer_reset(flatcc_json_printer_t *ctx) +{ + ctx->p = ctx->buf; + ctx->level = 0; + ctx->total = 0; + ctx->error = 0; +} + +/* + * A custom init function can be implemented with a custom flush + * function can be custom implemented. A few have been provided: + * init with external fixed size buffer, and init with dynamically + * growing buffer. + * + * Because there are a lot of small print functions, it is essentially + * always faster to print to local buffer than moving to io directly + * such as using fprintf or fwrite. The flush callback is used to + * move data when enough has been collected. + * + * `fp` should be of type `FILE *` but we do not enforce it here + * because it allows the header to be independent of + * when not required. If `fp` is null, it defaults to stdout. + * + * Returns -1 on alloc error (no cleanup needed), or 0 on success. + * Eventually the clear method must be called to return memory. + * + * The file pointer may be stdout or a custom file. The file pointer + * is not affected by reset or clear and should be closed manually. + * + * `set_flags` and related may be called subsequently to modify + * behavior. + */ +int flatcc_json_printer_init(flatcc_json_printer_t *ctx, void *fp); + +/* + * Prints to external buffer and sets overflow error if buffer is too + * small. Earlier content is then overwritten. A custom version of this + * function could flush the content to elsewhere before allowing the + * buffer content to be overwritten. The `buffers_size` must be large + * enough to hold `FLATCC_JSON_PRINT_RESERVED_SIZE` which is small but + * large enough value to hold entire numbers and the like. + * + * It is not strictly necessary to call clear because the buffer is + * external, but still good form and case the context type is changed + * later. + * + * Returns -1 on buffer size error (no cleanup needed), or 0 on success. + * + * `set_flags` and related may be called subsequently to modify + * behavior. + */ +int flatcc_json_printer_init_buffer(flatcc_json_printer_t *ctx, char *buffer, size_t buffer_size); + +/* + * Returns the current buffer pointer and also the content size in + * `buffer_size` if it is null. The operation is not very useful for + * file oriented printers (created with `init`) and will then only + * return the unflushed buffer content. For fixed size buffers + * (`init_buffer`), only the last content is available if the buffer + * overflowed. Works well with (`init_buffer`) when the dynamic buffer + * is be reused, otherwise `finalize_dynamic_buffer` could be more + * appropriate. + * + * The returned buffer is zero terminated. + * + * The returned pointer is only valid until next operation and should + * not deallocated manually. + */ +void *flatcc_json_printer_get_buffer(flatcc_json_printer_t *ctx, size_t *buffer_size); + +/* + * Set to non-zero if names and enum symbols can be unquoted thus + * diverging from standard JSON while remaining compatible with `flatc` + * JSON flavor. + */ +static inline void flatcc_json_printer_set_unquoted(flatcc_json_printer_t *ctx, int x) +{ + ctx->unquote = !!x; +} + +/* + * Set to non-zero if enums should always be printed as numbers. + * Otherwise enums are printed as a symbol for member values, and as + * numbers for other values. + * + * NOTE: this setting will not affect code generated with enum mapping + * disabled - statically disabling enum mapping is signficantly faster + * for enums, less so for for union types. + */ +static inline void flatcc_json_printer_set_noenum(flatcc_json_printer_t *ctx, int x) +{ + ctx->noenum = !!x; +} + +/* + * Override priting an existing scalar field if it equals the default value. + * Note that this setting is not mutually exclusive to `set_force_default`. + */ +static inline void flatcc_json_printer_set_skip_default(flatcc_json_printer_t *ctx, int x) +{ + ctx->skip_default = !!x; +} + +/* + * Override skipping absent scalar fields and print the default value. + * Note that this setting is not mutually exclusive to `set_skip_default`. + */ +static inline void flatcc_json_printer_set_force_default(flatcc_json_printer_t *ctx, int x) +{ + ctx->force_default = !!x; +} + + +/* + * Set pretty-print indentation in number of spaces. 0 (default) is + * compact with no spaces or linebreaks (default), anything above + * triggers pretty print. + */ +static inline void flatcc_json_printer_set_indent(flatcc_json_printer_t *ctx, uint8_t x) +{ + ctx->indent = x; +} + +/* + * Override the default compact valid JSON format with a + * pretty printed non-strict version. Enums are translated + * to names, which is also the default. + */ +static inline void flatcc_json_printer_set_nonstrict(flatcc_json_printer_t *ctx) +{ + flatcc_json_printer_set_indent(ctx, 2); + flatcc_json_printer_set_unquoted(ctx, 1); + flatcc_json_printer_set_noenum(ctx, 0); +} + +enum flatcc_json_printer_flags { + flatcc_json_printer_f_unquote = 1, + flatcc_json_printer_f_noenum = 2, + flatcc_json_printer_f_skip_default = 4, + flatcc_json_printer_f_force_default = 8, + flatcc_json_printer_f_pretty = 16, + flatcc_json_printer_f_nonstrict = 32, +}; + +/* + * May be called instead of setting operational modes individually. + * Formatting is strict quoted json witout pretty printing by default. + * + * flags are: + * + * `unquote`, + * `noenum`, + * `skip_default`, + * `force_default`, + * `pretty`, + * `nonstrict` + * + * `pretty` flag sets indentation to 2. + * `nonstrict` implies: `noenum`, `unquote`, `pretty`. + */ +static inline void flatcc_json_printer_set_flags(flatcc_json_printer_t *ctx, int flags) +{ + ctx->unquote = !!(flags & flatcc_json_printer_f_unquote); + ctx->noenum = !!(flags & flatcc_json_printer_f_noenum); + ctx->skip_default = !!(flags & flatcc_json_printer_f_skip_default); + ctx->force_default = !!(flags & flatcc_json_printer_f_force_default); + if (flags & flatcc_json_printer_f_pretty) { + flatcc_json_printer_set_indent(ctx, 2); + } + if (flags & flatcc_json_printer_f_nonstrict) { + flatcc_json_printer_set_nonstrict(ctx); + } +} + + +/* + * Detects if the conctext type uses dynamically allocated memory + * using malloc and realloc and frees any such memory. + * + * Not all context types needs to be cleared. + */ +void flatcc_json_printer_clear(flatcc_json_printer_t *ctx); + +/* + * Ensures that there ia always buffer capacity for priting the next + * primitive with delimiters. + * + * Only flushes complete flush units and is inexpensive to call. + * The content buffer has an extra reserve which ensures basic + * data types and delimiters can always be printed after a partial + * flush. At the end, a `flush` is required to flush the + * remaining incomplete buffer data. + * + * Numbers do not call partial flush but will always fit into the reserve + * capacity after a partial flush, also surrounded by delimiters. + * + * Variable length operations generally submit a partial flush so it is + * safe to print a number after a name without flushing, but vectors of + * numbers must (and do) issue a partial flush between elements. This is + * handled automatically but must be considered if using the primitives + * for special purposes. Because repeated partial flushes are very cheap + * this is only a concern for high performance applications. + * + * When identiation is enabled, partial flush is also automatically + * issued . + */ +static inline void flatcc_json_printer_flush_partial(flatcc_json_printer_t *ctx) +{ + if (ctx->p >= ctx->pflush) { + ctx->flush(ctx, 0); + } +} + +/* + * Flush the remaining data not flushed by partial flush. It is valid to + * call at any point if it is acceptable to have unaligned flush units, + * but this is not desireable if, for example, compression or encryption + * is added to the flush pipeline. + * + * Not called automatically at the end of printing a flatbuffer object + * in case more data needs to be appended without submitting incomplete + * flush units prematurely - for example adding a newline at the end. + * + * The flush behavior depeends on the underlying `ctx` object, for + * example dynamic buffers have no distinction between partial and full + * flushes - here it is merely ensured that the buffer always has a + * reserve capacity left. + * + * Returns the total printed size. + */ +static inline size_t flatcc_json_printer_flush(flatcc_json_printer_t *ctx) +{ + ctx->flush(ctx, 1); + return ctx->total; +} + +/* + * Helper functions to print anything into the json buffer. + * Strings are escaped. + * + * When pretty printing (indent > 0), level 0 has special significance - + * so if wrapping printed json in a manually printed container json + * object, these functions can help manage this. + */ + +/* Escaped and quoted string. */ +void flatcc_json_printer_string(flatcc_json_printer_t *ctx, const char *s, int n); +/* Unescaped and unquoted string. */ +void flatcc_json_printer_write(flatcc_json_printer_t *ctx, const char *s, int n); +/* Print a newline and issues a partial flush. */ +void flatcc_json_printer_nl(flatcc_json_printer_t *ctx); +/* Like numbers, a partial flush is not issued. */ +void flatcc_json_printer_char(flatcc_json_printer_t *ctx, char c); +/* Indents and issues a partial flush. */ +void flatcc_json_printer_indent(flatcc_json_printer_t *ctx); +/* Adjust identation level, usually +/-1. */ +void flatcc_json_printer_add_level(flatcc_json_printer_t *ctx, int n); +/* Returns current identation level (0 is top level). */ +int flatcc_json_printer_get_level(flatcc_json_printer_t *ctx); + +/* + * If called explicitly be aware that repeated calls to numeric + * printers may cause buffer overflow without flush in-between. + */ +void flatcc_json_printer_uint8(flatcc_json_printer_t *ctx, uint8_t v); +void flatcc_json_printer_uint16(flatcc_json_printer_t *ctx, uint16_t v); +void flatcc_json_printer_uint32(flatcc_json_printer_t *ctx, uint32_t v); +void flatcc_json_printer_uint64(flatcc_json_printer_t *ctx, uint64_t v); +void flatcc_json_printer_int8(flatcc_json_printer_t *ctx, int8_t v); +void flatcc_json_printer_int16(flatcc_json_printer_t *ctx, int16_t v); +void flatcc_json_printer_int32(flatcc_json_printer_t *ctx, int32_t v); +void flatcc_json_printer_int64(flatcc_json_printer_t *ctx, int64_t v); +void flatcc_json_printer_bool(flatcc_json_printer_t *ctx, int v); +void flatcc_json_printer_float(flatcc_json_printer_t *ctx, float v); +void flatcc_json_printer_double(flatcc_json_printer_t *ctx, double v); + +void flatcc_json_printer_enum(flatcc_json_printer_t *ctx, + const char *symbol, int len); + +/* + * Convenience function to add a trailing newline, flush the buffer, + * test for error and reset the context for reuse. + * + * Returns total size printed or < 0 on error. + * + * This function makes most sense for file oriented output. + * See also `finalize_dynamic_buffer`. + */ +static inline int flatcc_json_printer_finalize(flatcc_json_printer_t *ctx) +{ + int ret; + flatcc_json_printer_nl(ctx); + ret = (int)flatcc_json_printer_flush(ctx); + if (ctx->error) { + ret = -1; + } + flatcc_json_printer_reset(ctx); + return ret; +} + +/* + * Allocates a small buffer and grows it dynamically. + * Buffer survives past reset. To reduce size between uses, call clear + * followed by init call. To reuse buffer just call reset between uses. + * If `buffer_size` is 0 a sensible default is being used. The size is + * automatically rounded up to reserved size if too small. + * + * Returns -1 on alloc error (no cleanup needed), or 0 on success. + * Eventually the clear method must be called to return memory. + * + * `set_flags` and related may be called subsequently to modify + * behavior. + */ +int flatcc_json_printer_init_dynamic_buffer(flatcc_json_printer_t *ctx, size_t buffer_size); + +/* + * Similar to calling `finalize` but returns the buffer and does NOT + * reset, but rather clears printer object and the returned buffer must + * be deallocated with `free`. + * + * The returned buffer is zero terminated. + * + * NOTE: it is entirely optional to use this method. For repeated used + * of dynamic buffers, `newline` (or not) followed by `get_buffer` + * and `reset` will be an alternative. + * + * Stores the printed buffer size in `buffer_size` if it is not null. + * + * See also `get_dynamic_buffer`. + */ +void *flatcc_json_printer_finalize_dynamic_buffer(flatcc_json_printer_t *ctx, size_t *buffer_size); + + +/************************************************************* + * The following is normally only used by generated code. + *************************************************************/ + +typedef struct flatcc_json_printer_table_descriptor flatcc_json_printer_table_descriptor_t; + +struct flatcc_json_printer_table_descriptor { + const void *table; + const void *vtable; + int vsize; + int ttl; + int count; +}; + +typedef void flatcc_json_printer_table_f(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td); + +typedef void flatcc_json_printer_struct_f(flatcc_json_printer_t *ctx, + const void *p); + +/* Generated value to name map callbacks. */ +typedef void flatcc_json_printer_uint8_enum_f(flatcc_json_printer_t *ctx, uint8_t v); +typedef void flatcc_json_printer_uint16_enum_f(flatcc_json_printer_t *ctx, uint16_t v); +typedef void flatcc_json_printer_uint32_enum_f(flatcc_json_printer_t *ctx, uint32_t v); +typedef void flatcc_json_printer_uint64_enum_f(flatcc_json_printer_t *ctx, uint64_t v); +typedef void flatcc_json_printer_int8_enum_f(flatcc_json_printer_t *ctx, int8_t v); +typedef void flatcc_json_printer_int16_enum_f(flatcc_json_printer_t *ctx, int16_t v); +typedef void flatcc_json_printer_int32_enum_f(flatcc_json_printer_t *ctx, int32_t v); +typedef void flatcc_json_printer_int64_enum_f(flatcc_json_printer_t *ctx, int64_t v); +typedef void flatcc_json_printer_bool_enum_f(flatcc_json_printer_t *ctx, flatbuffers_bool_t v); + +#define __define_print_scalar_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _field(flatcc_json_printer_t *ctx, \ + flatcc_json_printer_table_descriptor_t *td, \ + int id, const char *name, int len, T v); + +#define __define_print_scalar_struct_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _struct_field(flatcc_json_printer_t *ctx,\ + int index, const void *p, size_t offset, \ + const char *name, int len); + +#define __define_print_enum_struct_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _enum_struct_field( \ + flatcc_json_printer_t *ctx, \ + int index, const void *p, size_t offset, \ + const char *name, int len, \ + flatcc_json_printer_ ## TN ##_enum_f *pf); + +#define __define_print_enum_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _enum_field(flatcc_json_printer_t *ctx, \ + flatcc_json_printer_table_descriptor_t *td, \ + int id, const char *name, int len, T v, \ + flatcc_json_printer_ ## TN ##_enum_f *pf); + +#define __define_print_scalar_vector_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _vector_field(flatcc_json_printer_t *ctx,\ + flatcc_json_printer_table_descriptor_t *td, \ + int id, const char *name, int len); + +#define __define_print_enum_vector_field_proto(TN, T) \ +void flatcc_json_printer_ ## TN ## _enum_vector_field( \ + flatcc_json_printer_t *ctx, \ + flatcc_json_printer_table_descriptor_t *td, \ + int id, const char *name, int len, \ + flatcc_json_printer_ ## TN ##_enum_f *pf); + +__define_print_scalar_field_proto(uint8, uint8_t) +__define_print_scalar_field_proto(uint16, uint16_t) +__define_print_scalar_field_proto(uint32, uint32_t) +__define_print_scalar_field_proto(uint64, uint64_t) +__define_print_scalar_field_proto(int8, int8_t) +__define_print_scalar_field_proto(int16, int16_t) +__define_print_scalar_field_proto(int32, int32_t) +__define_print_scalar_field_proto(int64, int64_t) +__define_print_scalar_field_proto(bool, flatbuffers_bool_t) +__define_print_scalar_field_proto(float, float) +__define_print_scalar_field_proto(double, double) + +__define_print_enum_field_proto(uint8, uint8_t) +__define_print_enum_field_proto(uint16, uint16_t) +__define_print_enum_field_proto(uint32, uint32_t) +__define_print_enum_field_proto(uint64, uint64_t) +__define_print_enum_field_proto(int8, int8_t) +__define_print_enum_field_proto(int16, int16_t) +__define_print_enum_field_proto(int32, int32_t) +__define_print_enum_field_proto(int64, int64_t) +__define_print_enum_field_proto(bool, flatbuffers_bool_t) + +__define_print_scalar_struct_field_proto(uint8, uint8_t) +__define_print_scalar_struct_field_proto(uint16, uint16_t) +__define_print_scalar_struct_field_proto(uint32, uint32_t) +__define_print_scalar_struct_field_proto(uint64, uint64_t) +__define_print_scalar_struct_field_proto(int8, int8_t) +__define_print_scalar_struct_field_proto(int16, int16_t) +__define_print_scalar_struct_field_proto(int32, int32_t) +__define_print_scalar_struct_field_proto(int64, int64_t) +__define_print_scalar_struct_field_proto(bool, flatbuffers_bool_t) +__define_print_scalar_struct_field_proto(float, float) +__define_print_scalar_struct_field_proto(double, double) + +__define_print_enum_struct_field_proto(uint8, uint8_t) +__define_print_enum_struct_field_proto(uint16, uint16_t) +__define_print_enum_struct_field_proto(uint32, uint32_t) +__define_print_enum_struct_field_proto(uint64, uint64_t) +__define_print_enum_struct_field_proto(int8, int8_t) +__define_print_enum_struct_field_proto(int16, int16_t) +__define_print_enum_struct_field_proto(int32, int32_t) +__define_print_enum_struct_field_proto(int64, int64_t) +__define_print_enum_struct_field_proto(bool, flatbuffers_bool_t) + +__define_print_scalar_vector_field_proto(uint8, uint8_t) +__define_print_scalar_vector_field_proto(uint16, uint16_t) +__define_print_scalar_vector_field_proto(uint32, uint32_t) +__define_print_scalar_vector_field_proto(uint64, uint64_t) +__define_print_scalar_vector_field_proto(int8, int8_t) +__define_print_scalar_vector_field_proto(int16, int16_t) +__define_print_scalar_vector_field_proto(int32, int32_t) +__define_print_scalar_vector_field_proto(int64, int64_t) +__define_print_scalar_vector_field_proto(bool, flatbuffers_bool_t) +__define_print_scalar_vector_field_proto(float, float) +__define_print_scalar_vector_field_proto(double, double) + +__define_print_enum_vector_field_proto(uint8, uint8_t) +__define_print_enum_vector_field_proto(uint16, uint16_t) +__define_print_enum_vector_field_proto(uint32, uint32_t) +__define_print_enum_vector_field_proto(uint64, uint64_t) +__define_print_enum_vector_field_proto(int8, int8_t) +__define_print_enum_vector_field_proto(int16, int16_t) +__define_print_enum_vector_field_proto(int32, int32_t) +__define_print_enum_vector_field_proto(int64, int64_t) +__define_print_enum_vector_field_proto(bool, flatbuffers_bool_t) + +/* + * If `fid` is null, the identifier is not checked and is allowed to be + * entirely absent. + * + * The buffer must at least be aligned to uoffset_t on systems that + * require aligned memory addresses (as always for flatbuffers). + */ +int flatcc_json_printer_table_as_root(flatcc_json_printer_t *ctx, + const void *buf, size_t bufsiz, const char *fid, + flatcc_json_printer_table_f *pf); + +int flatcc_json_printer_struct_as_root(flatcc_json_printer_t *ctx, + const void *buf, size_t bufsiz, const char *fid, + flatcc_json_printer_struct_f *pf); + +/* + * Call before and after enum flags to ensure proper quotation. Enum + * quotes may be configured runtime, but regardless of this, multiple + * flags may be forced to be quoted depending on compile time flag since + * not all parsers may be able to handle unquoted space separated values + * even if they handle non-strict unquoted json otherwise. + * + * Flags should only be called when not empty (0) and when there are no + * unknown flags in the value. Otherwise print the numeric value. The + * auto generated code deals with this. + * + * This bit twiddling hack may be useful: + * + * `multiple = 0 != (v & (v - 1);` + */ +void flatcc_json_printer_delimit_enum_flags(flatcc_json_printer_t *ctx, int multiple); + +/* The index increments from 0 to handle space. It is not the flag bit position. */ +void flatcc_json_printer_enum_flag(flatcc_json_printer_t *ctx, int index, const char *symbol, int len); + +/* A struct inside another struct, as opposed to inside a table or a root. */ +void flatcc_json_printer_embedded_struct_field(flatcc_json_printer_t *ctx, + int index, const void *p, size_t offset, + const char *name, int len, + flatcc_json_printer_struct_f pf); + +void flatcc_json_printer_struct_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + flatcc_json_printer_struct_f *pf); + +void flatcc_json_printer_string_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len); + +void flatcc_json_printer_string_vector_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len); + +void flatcc_json_printer_table_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + flatcc_json_printer_table_f pf); + +void flatcc_json_printer_struct_vector_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + size_t size, + flatcc_json_printer_struct_f pf); + +void flatcc_json_printer_table_vector_field(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + flatcc_json_printer_table_f pf); + +void flatcc_json_printer_struct_as_nested_root(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + const char *fid, + flatcc_json_printer_struct_f *pf); + +void flatcc_json_printer_table_as_nested_root(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + int id, const char *name, int len, + const char *fid, + flatcc_json_printer_table_f pf); + +int flatcc_json_printer_read_union_type( + flatcc_json_printer_table_descriptor_t *td, + int id); + +void flatcc_json_printer_union_type(flatcc_json_printer_t *ctx, + flatcc_json_printer_table_descriptor_t *td, + const char *name, int len, int type, + const char *type_name, int type_len); + +#endif /* FLATCC_JSON_PRINTER_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_portable.h b/Source/library/FBSUtil/flatcc/flatcc_portable.h new file mode 100644 index 0000000..f0a7a14 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_portable.h @@ -0,0 +1,4 @@ +#ifndef FLATCC_PORTABLE_H +#define FLATCC_PORTABLE_H +#include "flatcc/portable/portable.h" +#endif /* FLATCC_PORTABLE_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_rtconfig.h b/Source/library/FBSUtil/flatcc/flatcc_rtconfig.h new file mode 100644 index 0000000..2cb2da2 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_rtconfig.h @@ -0,0 +1,144 @@ +#ifndef FLATCC_RTCONFIG_H +#define FLATCC_RTCONFIG_H + + +/* Include portability layer here since all other files depend on it. */ +#ifdef FLATCC_PORTABLE +#include "flatcc/portable/portable.h" +#endif + +/* + * Fast printing and parsing of double. + * + * This requires the grisu3/grisu3_* files to be in the include path, + * otherwise strod and sprintf will be used (these needed anyway + * as fallback for cases not supported by grisu3). + */ +#ifndef FLATCC_USE_GRISU3 +#define FLATCC_USE_GRISU3 1 +#endif + +/* + * This requires compiler that has enabled marc=native or similar so + * __SSE4_2__ flag is defined. Otherwise it will have no effect. + * + * While SSE may be used for different purposes, it has (as of this + * writing) only be used to test the effect on JSON whitespace handling + * which improved, but not by a lot, assuming 64-bit unligned access is + * otherwise available: + * + * With 8 space indentation, the JSON benchmark handles 308K parse ops/sec + * while SSE ups that to 333 parse ops/sec or 336 if \r\n is also + * consumed by SSE. Disabling indentation leaves SSE spacing handling + * ineffective, and performance reaches 450K parse ops/sec and can + * improve further to 500+K parse ops/sec if inexact GRISU3 numbers are + * allowed (they are pretty accurate anyway, just not exact). This + * feature requires hacking a flag direct in the grisu3 double parsing + * lib directly and only mentioned for comparison. + * + * In conclusion SSE doesn't add a lot to JSON space handling at least. + * + * Disabled by default, but can be overriden by build system. + */ +#ifndef FLATCC_USE_SSE4_2 +#define FLATCC_USE_SSE4_2 0 +#endif + +/* + * The verifier only reports yes and no. The following setting + * enables assertions in debug builds. It must be compiled into + * the runtime library and is not normally the desired behavior. + * + * NOTE: enabling this can break test cases so use with build, not test. + */ +#if !defined(FLATCC_DEBUG_VERIFY) && !defined(NDEBUG) +#define FLATCC_DEBUG_VERIFY 0 +#endif + +/* + * Limit recursion level for tables. Actual level may be deeper + * when structs are deeply nested - but these are limited by the + * schema compiler. + */ +#ifndef FLATCC_JSON_PRINT_MAX_LEVELS +#define FLATCC_JSON_PRINT_MAX_LEVELS 100 +#endif + +/* + * Print float and double values with C99 hexadecimal floating point + * notation. This option is not valid JSON but it avoids precision + * loss, correctly handles NaN, +/-Infinity and is significantly faster + * to parse and print. Some JSON parsers rely on strtod which does + * support hexadecimal floating points when C99 compliant. + */ +#ifndef FLATCC_JSON_PRINT_HEX_FLOAT +#define FLATCC_JSON_PRINT_HEX_FLOAT 0 +#endif + +/* + * Always print multipe enum flags like `color: "Red Green"` + * even when unquote is selected as an option for single + * value like `color: Green`. Otherwise multiple values + * are printed as `color: Red Green`, but this could break + * some flatbuffer json parser. + */ +#ifndef FLATCC_JSON_PRINT_ALWAYS_QUOTE_MULTIPLE_FLAGS +#define FLATCC_JSON_PRINT_ALWAYS_QUOTE_MULTIPLE_FLAGS 1 +#endif + +/* + * The general nesting limit may be lower, but for skipping + * JSON we do not need to - we can set this high as it only + * costs a single char per level in a stack array. + */ +#ifndef FLATCC_JSON_PARSE_GENERIC_MAX_NEST +#define FLATCC_JSON_PARSE_GENERIC_MAX_NEST 512 +#endif + +/* Store value even if it is default. */ +#ifndef FLATCC_JSON_PARSE_FORCE_DEFAULTS +#define FLATCC_JSON_PARSE_FORCE_DEFAULTS 0 +#endif + +#ifndef FLATCC_JSON_PARSE_ALLOW_UNQUOTED +#define FLATCC_JSON_PARSE_ALLOW_UNQUOTED 1 +#endif + +/* + * Multiple enum values are by default not permitted unless + * quoted like `color: "Red Green" as per Googles flatc JSON + * parser while a single value like `color: Red` can be + * unquoted. Enabling this setting will allow `color: Red + * Green`, but only if FLATCC_JSON_PARSE_ALLOW_UNQUOTED is + * also enabled. + */ +#ifndef FLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST +#define FLATCC_JSON_PARSE_ALLOW_UNQUOTED_LIST 0 +#endif + +#ifndef FLATCC_JSON_PARSE_ALLOW_UNKNOWN_FIELD +#define FLATCC_JSON_PARSE_ALLOW_UNKNOWN_FIELD 1 +#endif + +#ifndef FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA +#define FLATCC_JSON_PARSE_ALLOW_TRAILING_COMMA 1 +#endif + +/* + * Just parse to the closing bracket '}' if set. + * Otherwise parse to end by consuming space and + * fail if anything but space follows. + */ +#ifndef FLATCC_PARSE_IGNORE_TRAILING_DATA +#define FLATCC_PARSE_IGNORE_TRAILING_DATA 0 +#endif + +/* + * Optimize to parse a lot of white space, but + * in most cases it probably slows parsing down. + */ +#ifndef FLATCC_JSON_PARSE_WIDE_SPACE +#define FLATCC_JSON_PARSE_WIDE_SPACE 0 +#endif + +#endif /* FLATCC_RTCONFIG_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_types.h b/Source/library/FBSUtil/flatcc/flatcc_types.h new file mode 100644 index 0000000..6182c14 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_types.h @@ -0,0 +1,87 @@ +#ifndef FLATCC_TYPES_H +#define FLATCC_TYPES_H + +#include + +#ifndef UINT8_MAX +#include +#endif + +/* + * This should match generated type declaratios in + * `flatbuffers_common_reader.h` (might have different name prefix). + * Read only generated code does not depend on library code, + * hence the duplication. + */ +#ifndef flatbuffers_types_defined +#define flatbuffers_types_defined + +/* + * uoffset_t and soffset_t must be same integer type, except for sign. + * They can be (u)int16_t, (u)int32_t, or (u)int64_t. + * The default is (u)int32_t. + * + * voffset_t is expected to be uint16_t, but can experimentally be + * compiled from uint8_t up to uint32_t. + * + * ID_MAX is the largest value that can index a vtable. The table size + * is given as voffset value. Each id represents a voffset value index + * from 0 to max inclusive. Space is required for two header voffset + * fields and the unaddressible highest index (due to the table size + * representation). For 16-bit voffsets this yields a max of 2^15 - 4, + * or (2^16 - 1) / 2 - 3. + */ + +#define flatbuffers_uoffset_t_defined +#define flatbuffers_soffset_t_defined +#define flatbuffers_voffset_t_defined +#define flatbuffers_utype_t_defined +#define flatbuffers_bool_t_defined +#define flatbuffers_thash_t_defined +#define flatbuffers_fid_t_defined + +/* uoffset_t is also used for vector and string headers. */ +#define FLATBUFFERS_UOFFSET_MAX UINT32_MAX +#define FLATBUFFERS_SOFFSET_MAX INT32_MAX +#define FLATBUFFERS_SOFFSET_MIN INT32_MIN +#define FLATBUFFERS_VOFFSET_MAX UINT16_MAX +#define FLATBUFFERS_UTYPE_MAX UINT8_MAX +/* Well - the max of the underlying type. */ +#define FLATBUFFERS_BOOL_MAX UINT8_MAX +#define FLATBUFFERS_THASH_MAX UINT32_MAX + +#define FLATBUFFERS_ID_MAX (FLATBUFFERS_VOFFSET_MAX / sizeof(flatbuffers_voffset_t) - 3) +/* Vectors of empty structs can yield div by zero, so we must guard against this. */ +#define FLATBUFFERS_COUNT_MAX(elem_size) (FLATBUFFERS_UOFFSET_MAX/((elem_size) == 0 ? 1 : (elem_size))) + +#define FLATBUFFERS_UOFFSET_WIDTH 32 +#define FLATBUFFERS_COUNT_WIDTH 32 +#define FLATBUFFERS_SOFFSET_WIDTH 32 +#define FLATBUFFERS_VOFFSET_WIDTH 16 +#define FLATBUFFERS_UTYPE_WIDTH 8 +#define FLATBUFFERS_BOOL_WIDTH 8 +#define FLATBUFFERS_THASH_WIDTH 32 + +#define FLATBUFFERS_TRUE 1 +#define FLATBUFFERS_FALSE 0 + +#define FLATBUFFERS_PROTOCOL_IS_LE 1 +#define FLATBUFFERS_PROTOCOL_IS_BE 0 + +typedef uint32_t flatbuffers_uoffset_t; +typedef int32_t flatbuffers_soffset_t; +typedef uint16_t flatbuffers_voffset_t; +typedef uint8_t flatbuffers_utype_t; +typedef uint8_t flatbuffers_bool_t; +typedef uint32_t flatbuffers_thash_t; + +static const flatbuffers_bool_t flatbuffers_true = FLATBUFFERS_TRUE; +static const flatbuffers_bool_t flatbuffers_false = FLATBUFFERS_FALSE; + +#define FLATBUFFERS_IDENTIFIER_SIZE (FLATBUFFERS_THASH_WIDTH / 8) + +typedef char flatbuffers_fid_t[FLATBUFFERS_IDENTIFIER_SIZE]; + +#endif /* flatbuffers_types_defined */ + +#endif /* FLATCC_TYPES_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_unaligned.h b/Source/library/FBSUtil/flatcc/flatcc_unaligned.h new file mode 100644 index 0000000..a53f47a --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_unaligned.h @@ -0,0 +1,8 @@ +#ifndef FLATCC_UNLIGNED_H +#define FLATCC_UNLIGNED_H + +#include "flatcc/portable/punaligned.h" + +#define FLATCC_ALLOW_UNALIGNED_ACCESS PORTABLE_UNALIGNED_ACCESS + +#endif /* FLATCC_UNLIGNED_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_verifier.h b/Source/library/FBSUtil/flatcc/flatcc_verifier.h new file mode 100644 index 0000000..92483b8 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_verifier.h @@ -0,0 +1,203 @@ +#ifndef FLATCC_VERIFIER_H +#define FLATCC_VERIFIER_H + +/* + * Runtime support for verifying flatbuffers. + * + * Link with the verifier implementation file. + * + * Note: + * + * 1) nested buffers will NOT have their identifier verified. + * The user may do so subsequently. The reason is in part because + * the information is not readily avaible without generated reader code, + * in part because the buffer might use a different, but valid, + * identifier and the user has no chance of specifiying this in the + * verifier code. The root verifier also doesn't assume a specific id + * but accepts a user supplied input which may be null. + * + * 2) All offsets in a buffer are verified for alignment relative to the + * buffer start, but the buffer itself is only assumed to aligned to + * uoffset_t. A reader should therefore ensure buffer alignment separately + * before reading the buffer. Nested buffers are in fact checked for + * alignment, but still only relative to the root buffer. + * + * 3) The max nesting level includes nested buffer nestings, so the + * verifier might fail even if the individual buffers are otherwise ok. + * This is to prevent abuse with lots of nested buffers. + * + * + * IMPORTANT: + * + * Even if verifier passes, the buffer may be invalid to access due to + * lack of alignemnt in memory, but the verifier is safe to call. + * + * NOTE: The buffer is not safe to modify after verification because an + * attacker may craft overlapping data structures such that modification + * of one field updates another in a way that violates the buffer + * constraints. This may also be caused by a clever compression scheme. + * + * It is likely faster to rewrite the table although this is also + * dangerous because an attacker (or even normal user) can draft a DAG + * that explodes when expanded carelesslessly. A safer approach is to + * hash all object references written and reuse those that match. This + * will expand references into other objects while bounding expansion + * and it will be safe to update assuming shared objects are ok to + * update. + * + */ + +#include "flatcc/flatcc_types.h" + +#define FLATCC_VERIFY_ERROR_MAP(XX)\ + XX(ok, "ok")\ + XX(buffer_header_too_small, "buffer header too small")\ + XX(identifier_mismatch, "identifier mismatch")\ + XX(max_nesting_level_reached, "max nesting level reached")\ + XX(required_field_missing, "required field missing")\ + XX(runtime_buffer_header_not_aligned, "runtime: buffer header not aligned")\ + XX(runtime_buffer_size_too_large, "runtime: buffer size too large")\ + XX(string_not_zero_terminated, "string not zero terminated")\ + XX(string_out_of_range, "string out of range")\ + XX(struct_out_of_range, "struct out of range")\ + XX(struct_size_overflow, "struct size overflow")\ + XX(struct_unaligned, "struct unaligned")\ + XX(table_field_not_aligned, "table field not aligned")\ + XX(table_field_out_of_range, "table field out of range")\ + XX(table_field_size_overflow, "table field size overflow")\ + XX(table_header_out_of_range_or_unaligned, "table header out of range or unaligned")\ + XX(table_offset_out_of_range_or_unaligned, "table offset out of range or unaligned")\ + XX(table_size_out_of_range, "table size out of range")\ + XX(type_field_absent_from_required_union_field, "type field absent from required union field")\ + XX(union_cannot_have_a_table_without_a_type, "union cannot have a table without a type")\ + XX(union_type_NONE_cannot_have_a_table, "union type NONE cannot have a table")\ + XX(vector_count_exceeds_representable_vector_size, "vector count exceeds representable vector size")\ + XX(vector_out_of_range, "vector out of range")\ + XX(vtable_header_out_of_range, "vtable header out of range")\ + XX(vtable_header_too_small, "vtable header too small")\ + XX(vtable_offset_out_of_range_or_unaligned, "vtable offset out of range or unaligned")\ + XX(vtable_size_out_of_range_or_unaligned, "vtable size out of range or unaligned")\ + XX(vtable_size_overflow, "vtable size overflow")\ + XX(not_supported, "not supported") + + +enum flatcc_verify_error_no { +#define XX(no, str) flatcc_verify_error_##no, + FLATCC_VERIFY_ERROR_MAP(XX) +#undef XX +}; + +#define flatcc_verify_ok flatcc_verify_error_ok + +const char *flatcc_verify_error_string(int err); + + +/* + * Type specific table verifier function that checks each known field + * for existence in the vtable and then calls the appropriate verifier + * function in this library. + * + * The table descriptor values have been verified for bounds, overflow, + * and alignment, but vtable entries after header must be verified + * for all fields the table verifier function understands. + * + * Calls other typespecific verifier functions recursively whenever a + * table field, union or table vector is encountered. + */ +typedef struct flatcc_table_verifier_descriptor flatcc_table_verifier_descriptor_t; +struct flatcc_table_verifier_descriptor { + /* Pointer to buffer. Not assumed to be aligned beyond uoffset_t. */ + const void *buf; + /* Vtable of current table. */ + const void *vtable; + /* Buffer size. */ + flatbuffers_uoffset_t end; + /* Table offset relative to buffer start */ + flatbuffers_uoffset_t table; + /* Table end relative to buffer start as per vtable[1] field. */ + flatbuffers_voffset_t tsize; + /* Size of vtable in bytes. */ + flatbuffers_voffset_t vsize; + /* Time to live: number nesting levels left before failure. */ + int ttl; +}; + +typedef int flatcc_table_verifier_f(flatcc_table_verifier_descriptor_t *td); + +typedef int flatcc_union_verifier_f(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, uint8_t type); + + +/* + * The `as_root` functions are normally the only functions called + * explicitly in this interface. + * + * If `fid` is null, the identifier is not checked and is allowed to be entirely absent. + * + * The buffer must at least be aligned to uoffset_t on systems that + * require aligned memory addresses. The buffer pointers alignment is + * not significant to internal verification of the buffer. + */ +int flatcc_verify_struct_as_root(const void *buf, size_t bufsiz, const char *fid, + uint16_t align, size_t size); + +int flatcc_verify_struct_as_typed_root(const void *buf, size_t bufsiz, flatbuffers_thash_t thash, + uint16_t align, size_t size); + +int flatcc_verify_table_as_root(const void *buf, size_t bufsiz, const char *fid, + flatcc_table_verifier_f *root_tvf); + +int flatcc_verify_table_as_typed_root(const void *buf, size_t bufsiz, flatbuffers_thash_t thash, + flatcc_table_verifier_f *root_tvf); +/* + * The buffer header is verified by any of the `_as_root` verifiers, but + * this function may be used as a quick sanity check. + */ +int flatcc_verify_buffer_header(const void *buf, size_t bufsiz, const char *fid); + +int flatcc_verify_typed_buffer_header(const void *buf, size_t bufsiz, flatbuffers_thash_t type_hash); + +/* + * The following functions are typically called by a generated table + * verifier function. + */ + +/* Scalar, enum or struct field. */ +int flatcc_verify_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, uint16_t align, size_t size); +/* Vector of scalars, enums or structs. */ +int flatcc_verify_vector_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, uint16_t align, size_t elem_size, size_t max_count); +int flatcc_verify_string_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required); +int flatcc_verify_string_vector_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required); +int flatcc_verify_table_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, flatcc_table_verifier_f tvf); +int flatcc_verify_table_vector_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, flatcc_table_verifier_f tvf); + +/* Table verifiers pass 0 as fid. */ +int flatcc_verify_struct_as_nested_root(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, const char *fid, + uint16_t align, size_t size); +int flatcc_verify_table_as_nested_root(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, const char *fid, + uint16_t align, flatcc_table_verifier_f tvf); + +/* + * A NONE type will not accept a table being present, and a required + * union will not accept a type field being absent, and an absent type + * field will not accept a table field being present. + * + * If the above checks out and the type is not NONE, the uvf callback + * is executed. It must test each known table type and silently accept + * any unknown table type for forward compatibility. A union table + * member is verified without the required flag because an absent table + * encodes a typed NULL value while an absent type field encodes a + * missing union which fails if required. + */ +int flatcc_verify_union_field(flatcc_table_verifier_descriptor_t *td, + flatbuffers_voffset_t id, int required, flatcc_union_verifier_f *uvf); + +#endif /* FLATCC_VERIFIER_H */ diff --git a/Source/library/FBSUtil/flatcc/flatcc_version.h b/Source/library/FBSUtil/flatcc/flatcc_version.h new file mode 100644 index 0000000..49b1547 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/flatcc_version.h @@ -0,0 +1,6 @@ +#define FLATCC_VERSION_TEXT "0.4.3-pre" +#define FLATCC_VERSION_MAJOR 0 +#define FLATCC_VERSION_MINOR 4 +#define FLATCC_VERSION_PATCH 3 +/* 1 or 0 */ +#define FLATCC_VERSION_RELEASED 0 diff --git a/Source/library/FBSUtil/flatcc/portable/LICENSE b/Source/library/FBSUtil/flatcc/portable/LICENSE new file mode 100644 index 0000000..bb7ca57 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com +Some files also Copyright author of MathGeoLib (https://github.com/juj) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. http://www.apache.org/licenses/LICENSE-2.0 diff --git a/Source/library/FBSUtil/flatcc/portable/README.md b/Source/library/FBSUtil/flatcc/portable/README.md new file mode 100644 index 0000000..f6038cb --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/README.md @@ -0,0 +1,54 @@ +A small library for adding C11 compatibility to older C compilers, but +only a small highly useful subset such as static assertions, inline +functions and alignment. + +Many compilers already have the required functionality but named +slightly different. + +In addition, compatibility with the Linux `` system file is +provided, and "punaligned.h" is provided for unaligned memory reads +which in part depends on endian support. + +The library also provides fast integer printing and floating point +printing and parsing optionally using the grisu3 algorithm, but can fall +back to strtod and related. The `pgrisu3` folder is header only and +excludes test cases found in the main grisu3 project the files were +extracted from. + +Integer conversion is not just an optimization. It is more difficult +than it would appear to portably parse an integer of known size such as +`uint64_t` up to at most n bytes which is needed for safe parsing. At +the same time, the sometimes significant performance gains warrants +custom implementations that might as well be done once and for all. + +Files can be included individually, or portable.h may be included to get +all functionality. If the compiler is C11 compliant, portable.h will not +include anything, except: it will provide a patch for static assertions +which clang does not fully support in all versions even with C11 flagged. + +The grisu3 header files are the runtime files for the Grisu3 floating +point conversion to/from text C port. Test coverage is provided separately. +This library can be used indirectly via pparsefp.h and pprintfp.h. + +The `pstatic_assert.h` file is often needed on C11 systems because the +compiler and standard library may support `_Static_assert` without +`static_assert`. For compilers without `_Static_assert`, a unique +identifier is needed for each assertion. This is done non-standard with +the `__COUNTER__` macro, but has a fallback to `pstatic_assert_scope.h` +for systems witout the `__COUNTER__` macro. Because of this fallback, +`pstatic_assert.h` needs to be included in every file using +`static_assert` in order to increment a scope counter, otherwise there +is a risk of assert identifier conflicts when `static_assert` happen on +the same line in different files. + +The `paligned_alloc.h` file implements the non-standard `aligned_free` +to match the C11 standard `aligned_alloc` call. `aligned_free` is +normally equivalent to `free`, but not on systems where `aligned_free` +cannot be implemented using a system provived `free` call. Use of +`aligned_free` is thus optional on some systems, but using it increases +general portablity at the cost of pure C11 compatibility. + +IMPORTANT NOTE: this library is not widely tested. It is intended to be +a starting point. Each use case should test on relevant target platforms +and if relevant send patches upstream. + diff --git a/Source/library/FBSUtil/flatcc/portable/grisu3_math.h b/Source/library/FBSUtil/flatcc/portable/grisu3_math.h new file mode 100644 index 0000000..d9c6fee --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/grisu3_math.h @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com + * Copyright author of MathGeoLib (https://github.com/juj) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. http://www.apache.org/licenses/LICENSE-2.0 + */ + +/* 2016-02-02: Updated by mikkelfj + * + * Extracted from MatGeoLib grisu3.c, Apache 2.0 license, and extended. + * + * This file is usually include via grisu3_print.h or grisu3_parse.h. + * + * The original MatGeoLib dtoa_grisu3 implementation is largely + * unchanged except for the uint64 to double cast. The remaining changes + * are file structure, name changes, and new additions for parsing: + * + * - Split into header files only: + * grisu3_math.h, grisu3_print.h, (added grisu3_parse.h) + * + * - names prefixed with grisu3_, grisu3_diy_fp_, GRISU3_. + * - added static to all functions. + * - disabled clang unused function warnings. + * - guarded to allow for alternative impl. + * - added extra numeric constants needed for parsing. + * - added dec_pow, cast_double_from_diy_fp. + * - changed some function names for consistency. + * - moved printing specific grisu3 functions to grisu3_print.h. + * - changed double to uint64 cast to avoid aliasing. + * - added new grisu3_parse.h for parsing doubles. + * - grisu3_print_double (dtoa_grisu3) format .1 as 0.1 needed for valid JSON output + * and grisu3_parse_double wouldn't consume it. + * - grsu3_print_double changed formatting to prefer 0.012 over 1.2e-2. + * + * These changes make it possible to include the files as headers only + * in other software libraries without risking name conflicts, and to + * extend the implementation with a port of Googles Double Conversion + * strtod functionality for parsing doubles. + * + * Extracted from: rev. 915501a / Dec 22, 2015 + * + * MathGeoLib License: http://www.apache.org/licenses/LICENSE-2.0.html + */ + +#ifndef GRISU3_MATH_H +#define GRISU3_MATH_H + +/* Guarded to allow inclusion of pstdint.h first, if stdint.h is not supported. */ +#ifndef UINT8_MAX +#include /* uint64_t etc. */ +#endif +#include /* assert */ + +#ifdef _MSC_VER +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer */ +#endif + +#define GRISU3_D64_SIGN 0x8000000000000000ULL +#define GRISU3_D64_EXP_MASK 0x7FF0000000000000ULL +#define GRISU3_D64_FRACT_MASK 0x000FFFFFFFFFFFFFULL +#define GRISU3_D64_IMPLICIT_ONE 0x0010000000000000ULL +#define GRISU3_D64_EXP_POS 52 +#define GRISU3_D64_EXP_BIAS 1075 +#define GRISU3_D64_DENORM_EXP (-GRISU3_D64_EXP_BIAS + 1) +#define GRISU3_DIY_FP_FRACT_SIZE 64 +#define GRISU3_D_1_LOG2_10 0.30102999566398114 /* 1 / lg(10) */ +#define GRISU3_MIN_TARGET_EXP -60 +#define GRISU3_MASK32 0xFFFFFFFFULL +#define GRISU3_MIN_CACHED_EXP -348 +#define GRISU3_MAX_CACHED_EXP 340 +#define GRISU3_CACHED_EXP_STEP 8 +#define GRISU3_D64_MAX_DEC_EXP 309 +#define GRISU3_D64_MIN_DEC_EXP -324 +#define GRISU3_D64_INF GRISU3_D64_EXP_MASK + +#define GRISU3_MIN(x,y) ((x) <= (y) ? (x) : (y)) +#define GRISU3_MAX(x,y) ((x) >= (y) ? (x) : (y)) + + +typedef struct grisu3_diy_fp +{ + uint64_t f; + int e; +} grisu3_diy_fp_t; + +typedef struct grisu3_diy_fp_power +{ + uint64_t fract; + int16_t b_exp, d_exp; +} grisu3_diy_fp_power_t; + +typedef union { + uint64_t u64; + double d64; +} grisu3_cast_double_t; + +static uint64_t grisu3_cast_uint64_from_double(double d) +{ + grisu3_cast_double_t cd; + cd.d64 = d; + return cd.u64; +} + +static double grisu3_cast_double_from_uint64(uint64_t u) +{ + grisu3_cast_double_t cd; + cd.u64 = u; + return cd.d64; +} + +#define grisu3_double_infinity grisu3_cast_double_from_uint64(GRISU3_D64_INF) +#define grisu3_double_nan grisu3_cast_double_from_uint64(GRISU3_D64_INF + 1) + +static const grisu3_diy_fp_power_t grisu3_diy_fp_pow_cache[] = +{ + { 0xfa8fd5a0081c0288ULL, -1220, -348 }, + { 0xbaaee17fa23ebf76ULL, -1193, -340 }, + { 0x8b16fb203055ac76ULL, -1166, -332 }, + { 0xcf42894a5dce35eaULL, -1140, -324 }, + { 0x9a6bb0aa55653b2dULL, -1113, -316 }, + { 0xe61acf033d1a45dfULL, -1087, -308 }, + { 0xab70fe17c79ac6caULL, -1060, -300 }, + { 0xff77b1fcbebcdc4fULL, -1034, -292 }, + { 0xbe5691ef416bd60cULL, -1007, -284 }, + { 0x8dd01fad907ffc3cULL, -980, -276 }, + { 0xd3515c2831559a83ULL, -954, -268 }, + { 0x9d71ac8fada6c9b5ULL, -927, -260 }, + { 0xea9c227723ee8bcbULL, -901, -252 }, + { 0xaecc49914078536dULL, -874, -244 }, + { 0x823c12795db6ce57ULL, -847, -236 }, + { 0xc21094364dfb5637ULL, -821, -228 }, + { 0x9096ea6f3848984fULL, -794, -220 }, + { 0xd77485cb25823ac7ULL, -768, -212 }, + { 0xa086cfcd97bf97f4ULL, -741, -204 }, + { 0xef340a98172aace5ULL, -715, -196 }, + { 0xb23867fb2a35b28eULL, -688, -188 }, + { 0x84c8d4dfd2c63f3bULL, -661, -180 }, + { 0xc5dd44271ad3cdbaULL, -635, -172 }, + { 0x936b9fcebb25c996ULL, -608, -164 }, + { 0xdbac6c247d62a584ULL, -582, -156 }, + { 0xa3ab66580d5fdaf6ULL, -555, -148 }, + { 0xf3e2f893dec3f126ULL, -529, -140 }, + { 0xb5b5ada8aaff80b8ULL, -502, -132 }, + { 0x87625f056c7c4a8bULL, -475, -124 }, + { 0xc9bcff6034c13053ULL, -449, -116 }, + { 0x964e858c91ba2655ULL, -422, -108 }, + { 0xdff9772470297ebdULL, -396, -100 }, + { 0xa6dfbd9fb8e5b88fULL, -369, -92 }, + { 0xf8a95fcf88747d94ULL, -343, -84 }, + { 0xb94470938fa89bcfULL, -316, -76 }, + { 0x8a08f0f8bf0f156bULL, -289, -68 }, + { 0xcdb02555653131b6ULL, -263, -60 }, + { 0x993fe2c6d07b7facULL, -236, -52 }, + { 0xe45c10c42a2b3b06ULL, -210, -44 }, + { 0xaa242499697392d3ULL, -183, -36 }, + { 0xfd87b5f28300ca0eULL, -157, -28 }, + { 0xbce5086492111aebULL, -130, -20 }, + { 0x8cbccc096f5088ccULL, -103, -12 }, + { 0xd1b71758e219652cULL, -77, -4 }, + { 0x9c40000000000000ULL, -50, 4 }, + { 0xe8d4a51000000000ULL, -24, 12 }, + { 0xad78ebc5ac620000ULL, 3, 20 }, + { 0x813f3978f8940984ULL, 30, 28 }, + { 0xc097ce7bc90715b3ULL, 56, 36 }, + { 0x8f7e32ce7bea5c70ULL, 83, 44 }, + { 0xd5d238a4abe98068ULL, 109, 52 }, + { 0x9f4f2726179a2245ULL, 136, 60 }, + { 0xed63a231d4c4fb27ULL, 162, 68 }, + { 0xb0de65388cc8ada8ULL, 189, 76 }, + { 0x83c7088e1aab65dbULL, 216, 84 }, + { 0xc45d1df942711d9aULL, 242, 92 }, + { 0x924d692ca61be758ULL, 269, 100 }, + { 0xda01ee641a708deaULL, 295, 108 }, + { 0xa26da3999aef774aULL, 322, 116 }, + { 0xf209787bb47d6b85ULL, 348, 124 }, + { 0xb454e4a179dd1877ULL, 375, 132 }, + { 0x865b86925b9bc5c2ULL, 402, 140 }, + { 0xc83553c5c8965d3dULL, 428, 148 }, + { 0x952ab45cfa97a0b3ULL, 455, 156 }, + { 0xde469fbd99a05fe3ULL, 481, 164 }, + { 0xa59bc234db398c25ULL, 508, 172 }, + { 0xf6c69a72a3989f5cULL, 534, 180 }, + { 0xb7dcbf5354e9beceULL, 561, 188 }, + { 0x88fcf317f22241e2ULL, 588, 196 }, + { 0xcc20ce9bd35c78a5ULL, 614, 204 }, + { 0x98165af37b2153dfULL, 641, 212 }, + { 0xe2a0b5dc971f303aULL, 667, 220 }, + { 0xa8d9d1535ce3b396ULL, 694, 228 }, + { 0xfb9b7cd9a4a7443cULL, 720, 236 }, + { 0xbb764c4ca7a44410ULL, 747, 244 }, + { 0x8bab8eefb6409c1aULL, 774, 252 }, + { 0xd01fef10a657842cULL, 800, 260 }, + { 0x9b10a4e5e9913129ULL, 827, 268 }, + { 0xe7109bfba19c0c9dULL, 853, 276 }, + { 0xac2820d9623bf429ULL, 880, 284 }, + { 0x80444b5e7aa7cf85ULL, 907, 292 }, + { 0xbf21e44003acdd2dULL, 933, 300 }, + { 0x8e679c2f5e44ff8fULL, 960, 308 }, + { 0xd433179d9c8cb841ULL, 986, 316 }, + { 0x9e19db92b4e31ba9ULL, 1013, 324 }, + { 0xeb96bf6ebadf77d9ULL, 1039, 332 }, + { 0xaf87023b9bf0ee6bULL, 1066, 340 } +}; + +/* Avoid dependence on lib math to get (int)ceil(v) */ +static int grisu3_iceil(double v) +{ + int k = (int)v; + if (v < 0) return k; + return v - k == 0 ? k : k + 1; +} + +static int grisu3_diy_fp_cached_pow(int exp, grisu3_diy_fp_t *p) +{ + int k = grisu3_iceil((exp+GRISU3_DIY_FP_FRACT_SIZE-1) * GRISU3_D_1_LOG2_10); + int i = (k-GRISU3_MIN_CACHED_EXP-1) / GRISU3_CACHED_EXP_STEP + 1; + p->f = grisu3_diy_fp_pow_cache[i].fract; + p->e = grisu3_diy_fp_pow_cache[i].b_exp; + return grisu3_diy_fp_pow_cache[i].d_exp; +} + +static grisu3_diy_fp_t grisu3_diy_fp_minus(grisu3_diy_fp_t x, grisu3_diy_fp_t y) +{ + grisu3_diy_fp_t d; d.f = x.f - y.f; d.e = x.e; + assert(x.e == y.e && x.f >= y.f); + return d; +} + +static grisu3_diy_fp_t grisu3_diy_fp_multiply(grisu3_diy_fp_t x, grisu3_diy_fp_t y) +{ + uint64_t a, b, c, d, ac, bc, ad, bd, tmp; + grisu3_diy_fp_t r; + a = x.f >> 32; b = x.f & GRISU3_MASK32; + c = y.f >> 32; d = y.f & GRISU3_MASK32; + ac = a*c; bc = b*c; + ad = a*d; bd = b*d; + tmp = (bd >> 32) + (ad & GRISU3_MASK32) + (bc & GRISU3_MASK32); + tmp += 1U << 31; /* round */ + r.f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); + r.e = x.e + y.e + 64; + return r; +} + +static grisu3_diy_fp_t grisu3_diy_fp_normalize(grisu3_diy_fp_t n) +{ + assert(n.f != 0); + while(!(n.f & 0xFFC0000000000000ULL)) { n.f <<= 10; n.e -= 10; } + while(!(n.f & GRISU3_D64_SIGN)) { n.f <<= 1; --n.e; } + return n; +} + +static grisu3_diy_fp_t grisu3_cast_diy_fp_from_double(double d) +{ + grisu3_diy_fp_t fp; + uint64_t u64 = grisu3_cast_uint64_from_double(d); + if (!(u64 & GRISU3_D64_EXP_MASK)) { fp.f = u64 & GRISU3_D64_FRACT_MASK; fp.e = 1 - GRISU3_D64_EXP_BIAS; } + else { fp.f = (u64 & GRISU3_D64_FRACT_MASK) + GRISU3_D64_IMPLICIT_ONE; fp.e = (int)((u64 & GRISU3_D64_EXP_MASK) >> GRISU3_D64_EXP_POS) - GRISU3_D64_EXP_BIAS; } + return fp; +} + +static double grisu3_cast_double_from_diy_fp(grisu3_diy_fp_t n) +{ + const uint64_t hidden_bit = GRISU3_D64_IMPLICIT_ONE; + const uint64_t frac_mask = GRISU3_D64_FRACT_MASK; + const int denorm_exp = GRISU3_D64_DENORM_EXP; + const int exp_bias = GRISU3_D64_EXP_BIAS; + const int exp_pos = GRISU3_D64_EXP_POS; + + grisu3_diy_fp_t v = n; + uint64_t e_biased; + + while (v.f > hidden_bit + frac_mask) { + v.f >>= 1; + ++v.e; + } + if (v.e < denorm_exp) { + return 0.0; + } + while (v.e > denorm_exp && (v.f & hidden_bit) == 0) { + v.f <<= 1; + --v.e; + } + if (v.e == denorm_exp && (v.f & hidden_bit) == 0) { + e_biased = 0; + } else { + e_biased = (uint64_t)(v.e + exp_bias); + } + return grisu3_cast_double_from_uint64((v.f & frac_mask) | (e_biased << exp_pos)); +} + +/* pow10_cache[i] = 10^(i-1) */ +static const unsigned int grisu3_pow10_cache[] = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + +static int grisu3_largest_pow10(uint32_t n, int n_bits, uint32_t *power) +{ + int guess = ((n_bits + 1) * 1233 >> 12) + 1/*skip first entry*/; + if (n < grisu3_pow10_cache[guess]) --guess; /* We don't have any guarantees that 2^n_bits <= n. */ + *power = grisu3_pow10_cache[guess]; + return guess; +} + +#endif /* GRISU3_MATH_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/grisu3_parse.h b/Source/library/FBSUtil/flatcc/portable/grisu3_parse.h new file mode 100644 index 0000000..c00eea2 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/grisu3_parse.h @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. http://www.apache.org/licenses/LICENSE-2.0 + */ + +/* + * Port of parts of Google Double Conversion strtod functionality + * but with fallback to strtod instead of a bignum implementation. + * + * Based on grisu3 math from MathGeoLib. + * + * See also grisu3_math.h comments. + */ + +#ifndef GRISU3_PARSE_H +#define GRISU3_PARSE_H + +#ifndef UINT8_MAX +#include +#endif + +#include +#include +#include + +#include "grisu3_math.h" + +/* + * The maximum number characters a valid number may contain. The parse + * fails if the input length is longer but the character after max len + * was part of the number. + * + * The length should not be set too high because it protects against + * overflow in the exponent part derived from the input length. + */ +#define GRISU3_NUM_MAX_LEN 1000 + +/* + * The lightweight "portable" C library recognizes grisu3 support if + * included first. + */ +#define grisu3_parse_double_is_defined 1 + +/* + * Disable to compare performance and to test diy_fp algorithm in + * broader range. + */ +#define GRISU3_PARSE_FAST_CASE + +/* May result in a one off error, otherwise when uncertain, fall back to strtod. */ +//#define GRISU3_PARSE_ALLOW_ERROR + + +/* + * The dec output exponent jumps in 8, so the result is offset at most + * by 7 when the input is within range. + */ +static int grisu3_diy_fp_cached_dec_pow(int d_exp, grisu3_diy_fp_t *p) +{ + const int cached_offset = -GRISU3_MIN_CACHED_EXP; + const int d_exp_dist = GRISU3_CACHED_EXP_STEP; + int i, a_exp; + + assert(GRISU3_MIN_CACHED_EXP <= d_exp); + assert(d_exp < GRISU3_MAX_CACHED_EXP + d_exp_dist); + + i = (d_exp + cached_offset) / d_exp_dist; + a_exp = grisu3_diy_fp_pow_cache[i].d_exp; + p->f = grisu3_diy_fp_pow_cache[i].fract; + p->e = grisu3_diy_fp_pow_cache[i].b_exp; + + assert(a_exp <= d_exp); + assert(d_exp < a_exp + d_exp_dist); + + return a_exp; +} + +/* + * Ported from google double conversion strtod using + * MathGeoLibs diy_fp functions for grisu3 in C. + * + * ulp_half_error is set if needed to trunacted non-zero trialing + * characters. + * + * The actual value we need to encode is: + * + * (sign ? -1 : 1) * fraction * 2 ^ (exponent - fraction_exp) + * where exponent is the base 10 exponent assuming the decimal point is + * after the first digit. fraction_exp is the base 10 magnitude of the + * fraction or number of significant digits - 1. + * + * If the exponent is between 0 and 22 and the fraction is encoded in + * the lower 53 bits (the largest bit is implicit in a double, but not + * in this fraction), then the value can be trivially converted to + * double without loss of precision. If the fraction was in fact + * multiplied by trailing zeroes that we didn't convert to exponent, + * we there are larger values the 53 bits that can also be encoded + * trivially - but then it is better to handle this during parsing + * if it is worthwhile. We do not optimize for this here, because it + * can be done in a simple check before calling, and because it might + * not be worthwile to do at all since it cery likely will fail for + * numbers printed to be convertible back to double without loss. + * + * Returns 0 if conversion was not exact. In that case the vale is + * either one smaller than the correct one, or the correct one. + * + * Exponents must be range protected before calling otherwise cached + * powers will blow up. + * + * Google Double Conversion seems to prefer the following notion: + * + * x >= 10^309 => +Inf + * x <= 10^-324 => 0, + * + * max double: HUGE_VAL = 1.7976931348623157 * 10^308 + * min double: 4.9406564584124654 * 10^-324 + * + * Values just below or above min/max representable number + * may round towards large/small non-Inf/non-neg values. + * + * but `strtod` seems to return +/-HUGE_VAL on overflow? + */ +int grisu3_diy_fp_encode_double(uint64_t fraction, int exponent, int fraction_exp, int ulp_half_error, double *result) +{ + /* + * Error is measures in fractions of integers, so we scale up to get + * some resolution to represent error expressions. + */ + const int log2_error_one = 3; + const int error_one = 1 << log2_error_one; + const int denorm_exp = GRISU3_D64_DENORM_EXP; + const uint64_t hidden_bit = GRISU3_D64_IMPLICIT_ONE; + const int diy_size = GRISU3_DIY_FP_FRACT_SIZE; + const int max_digits = 19; + + int error = ulp_half_error ? error_one / 2 : 0; + int d_exp = (exponent - fraction_exp); + int a_exp; + int o_exp; + grisu3_diy_fp_t v = { fraction, 0 }; + grisu3_diy_fp_t cp; + grisu3_diy_fp_t rounded; + int mag; + int prec; + int prec_bits; + int half_way; + + /* When fractions in a double aren't stored with implicit msb fraction bit. */ + + /* Shift fraction to msb. */ + v = grisu3_diy_fp_normalize(v); + /* The half point error moves up while the exponent moves down. */ + error <<= -v.e; + + a_exp = grisu3_diy_fp_cached_dec_pow(d_exp, &cp); + + /* Interpolate between cached powers at distance 8. */ + if (a_exp != d_exp) { + int adj_exp = d_exp - a_exp - 1; + static grisu3_diy_fp_t cp_10_lut[] = { + { 0xa000000000000000ULL, -60 }, + { 0xc800000000000000ULL, -57 }, + { 0xfa00000000000000ULL, -54 }, + { 0x9c40000000000000ULL, -50 }, + { 0xc350000000000000ULL, -47 }, + { 0xf424000000000000ULL, -44 }, + { 0x9896800000000000ULL, -40 }, + }; + assert(adj_exp >= 0 && adj_exp < 7); + v = grisu3_diy_fp_multiply(v, cp_10_lut[adj_exp]); + + /* 20 decimal digits won't always fit in 64 bit. + * (`fraction_exp` is one less than significant decimal + * digits in fraction, e.g. 1 * 10e0). + * If we cannot fit, introduce 1/2 ulp error + * (says double conversion reference impl.) */ + if (1 + fraction_exp + adj_exp > max_digits) { + error += error_one / 2; + } + } + + v = grisu3_diy_fp_multiply(v, cp); + /* + * Google double conversion claims that: + * + * The error introduced by a multiplication of a*b equals + * error_a + error_b + error_a*error_b/2^64 + 0.5 + * Substituting a with 'input' and b with 'cached_power' we have + * error_b = 0.5 (all cached powers have an error of less than 0.5 ulp), + * error_ab = 0 or 1 / error_oner > error_a*error_b/ 2^64 + * + * which in our encoding becomes: + * error_a = error_one/2 + * error_ab = 1 / error_one (rounds up to 1 if error != 0, or 0 * otherwise) + * fixed_error = error_one/2 + * + * error += error_a + fixed_error + (error ? 1 : 0) + * + * (this isn't entirely clear, but that is as close as we get). + */ + error += error_one + (error ? 1 : 0); + + o_exp = v.e; + v = grisu3_diy_fp_normalize(v); + /* Again, if we shift the significant bits, the error moves along. */ + error <<= o_exp - v.e; + + /* + * The value `v` is bounded by 2^mag which is 64 + v.e. because we + * just normalized it by shifting towards msb. + */ + mag = diy_size + v.e; + + /* The effective magnitude of the IEEE double representation. */ + mag = mag >= diy_size + denorm_exp ? diy_size : mag <= denorm_exp ? 0 : mag - denorm_exp; + prec = diy_size - mag; + if (prec + log2_error_one >= diy_size) { + int e_scale = prec + log2_error_one - diy_size - 1; + v.f >>= e_scale; + v.e += e_scale; + error = (error >> e_scale) + 1 + error_one; + prec -= e_scale; + } + rounded.f = v.f >> prec; + rounded.e = v.e + prec; + prec_bits = (v.f & ((uint64_t)1 << (prec - 1))) * error_one; + half_way = ((uint64_t)1 << (prec - 1)) * error_one; + if (prec >= half_way + error) { + rounded.f++; + /* Prevent overflow. */ + if (rounded.f & (hidden_bit << 1)) { + rounded.f >>= 1; + rounded.e += 1; + } + } + *result = grisu3_cast_double_from_diy_fp(rounded); + return half_way - error >= prec_bits || prec_bits >= half_way + error; +} + +/* + * `end` is unchanged if number is handled natively, or it is the result + * of strtod parsing in case of fallback. + */ +static const char *grisu3_encode_double(const char *buf, const char *end, int sign, uint64_t fraction, int exponent, int fraction_exp, int ulp_half_error, double *result) +{ + const int max_d_exp = GRISU3_D64_MAX_DEC_EXP; + const int min_d_exp = GRISU3_D64_MIN_DEC_EXP; + const double infinity = (double)GRISU3_D64_INF; + + char *v_end; + + /* Both for user experience, and to protect internal power table lookups. */ + if (fraction == 0 || exponent < min_d_exp) { + *result = 0.0; + goto done; + } + if (exponent - 1 > max_d_exp) { + *result = infinity; + goto done; + } + + /* + * `exponent` is the normalized value, fraction_exp is the size of + * the representation in the `fraction value`, or one less than + * number of significant digits. + * + * If the final value can be kept in 53 bits and we can avoid + * division, then we can convert to double quite fast. + * + * ulf_half_error only happens when fraction is maxed out, so + * fraction_exp > 22 by definition. + * + * fraction_exp >= 0 always. + * + * http://www.exploringbinary.com/fast-path-decimal-to-floating-point-conversion/ + */ + + +#ifdef GRISU3_PARSE_FAST_CASE + if (fraction < (1ULL << 53) && exponent >= 0 && exponent <= 22) { + double v = (double)fraction; + /* Multiplying by 1e-k instead of dividing by 1ek results in rounding error. */ + switch (exponent - fraction_exp) { + case -22: v /= 1e22; break; + case -21: v /= 1e21; break; + case -20: v /= 1e20; break; + case -19: v /= 1e19; break; + case -18: v /= 1e18; break; + case -17: v /= 1e17; break; + case -16: v /= 1e16; break; + case -15: v /= 1e15; break; + case -14: v /= 1e14; break; + case -13: v /= 1e13; break; + case -12: v /= 1e12; break; + case -11: v /= 1e11; break; + case -10: v /= 1e10; break; + case -9: v /= 1e9; break; + case -8: v /= 1e8; break; + case -7: v /= 1e7; break; + case -6: v /= 1e6; break; + case -5: v /= 1e5; break; + case -4: v /= 1e4; break; + case -3: v /= 1e3; break; + case -2: v /= 1e2; break; + case -1: v /= 1e1; break; + case 0: break; + case 1: v *= 1e1; break; + case 2: v *= 1e2; break; + case 3: v *= 1e3; break; + case 4: v *= 1e4; break; + case 5: v *= 1e5; break; + case 6: v *= 1e6; break; + case 7: v *= 1e7; break; + case 8: v *= 1e8; break; + case 9: v *= 1e9; break; + case 10: v *= 1e10; break; + case 11: v *= 1e11; break; + case 12: v *= 1e12; break; + case 13: v *= 1e13; break; + case 14: v *= 1e14; break; + case 15: v *= 1e15; break; + case 16: v *= 1e16; break; + case 17: v *= 1e17; break; + case 18: v *= 1e18; break; + case 19: v *= 1e19; break; + case 20: v *= 1e20; break; + case 21: v *= 1e21; break; + case 22: v *= 1e22; break; + } + *result = v; + goto done; + } +#endif + + if (grisu3_diy_fp_encode_double(fraction, exponent, fraction_exp, ulp_half_error, result)) { + goto done; + } +#ifdef GRISU3_PARSE_ALLOW_ERROR + goto done; +#endif + *result = strtod(buf, &v_end); + if (v_end < end) { + return v_end; + } + return end; +done: + if (sign) { + *result = -*result; + } + return end; +} + +/* + * Returns buf if number wasn't matched, or null if number starts ok + * but contains invalid content. + */ +static const char *grisu3_parse_hex_fp(const char *buf, const char *end, int sign, double *result) +{ + (void)buf; + (void)end; + (void)sign; + *result = 0.0; + /* Not currently supported. */ + return buf; +} + +/* + * Returns end pointer on success, or null, or buf if start is not a number. + * Sets result to 0.0 on error. + * Reads up to len + 1 bytes from buffer where len + 1 must not be a + * valid part of a number, but all of buf, buf + len need not be a + * number. Leading whitespace is NOT valid. + * Very small numbers are truncated to +/-0.0 and numerically very large + * numbers are returns as +/-infinity. + * + * A value must not end or begin with '.' (like JSON), but can have + * leading zeroes (unlike JSON). A single leading zero followed by + * an encoding symbol may or may not be interpreted as a non-decimal + * encoding prefix, e.g. 0x, but a leading zero followed by a digit is + * NOT interpreted as octal. + * A single leading negative sign may appear before digits, but positive + * sign is not allowed and space after the sign is not allowed. + * At most the first 1000 characters of the input is considered. + */ +static const char *grisu3_parse_double(const char *buf, int len, double *result) +{ + const char *mark, *k, *end; + int sign = 0, esign = 0; + uint64_t fraction = 0; + int exponent = 0; + int ee = 0; + int fraction_exp = 0; + int ulp_half_error = 0; + + *result = 0.0; + + end = buf + len + 1; + + /* Failsafe for exponent overflow. */ + if (len > GRISU3_NUM_MAX_LEN) { + end = buf + GRISU3_NUM_MAX_LEN + 1; + } + + if (buf == end) { + return buf; + } + mark = buf; + if (*buf == '-') { + ++buf; + sign = 1; + if (buf == end) { + return 0; + } + } + if (*buf == '0') { + ++buf; + /* | 0x20 is lower case ASCII. */ + if (buf != end && (*buf | 0x20) == 'x') { + k = grisu3_parse_hex_fp(buf, end, sign, result); + if (k == buf) { + return mark; + } + return k; + } + /* Not worthwhile, except for getting the scale of integer part. */ + while (buf != end && *buf == '0') { + ++buf; + } + } else { + if (*buf < '1' || *buf > '9') { + /* + * If we didn't see a sign, just don't recognize it as + * number, otherwise make it an error. + */ + return sign ? 0 : mark; + } + fraction = *buf++ - '0'; + } + k = buf; + /* + * We do not catch trailing zeroes when there is no decimal point. + * This misses an opportunity for moving the exponent down into the + * fast case. But it is unlikely to be worthwhile as it complicates + * parsing. + */ + while (buf != end && *buf >= '0' && *buf <= '9') { + if (fraction >= UINT64_MAX / 10) { + fraction += *buf >= '5'; + ulp_half_error = 1; + break; + } + fraction = fraction * 10 + *buf++ - '0'; + } + fraction_exp = (int)(buf - k); + /* Skip surplus digits. Trailing zero does not introduce error. */ + while (buf != end && *buf == '0') { + ++exponent; + ++buf; + } + if (buf != end && *buf >= '1' && *buf <= '9') { + ulp_half_error = 1; + ++exponent; + ++buf; + while (buf != end && *buf >= '0' && *buf <= '9') { + ++exponent; + ++buf; + } + } + if (buf != end && *buf == '.') { + ++buf; + k = buf; + if (*buf < '0' || *buf > '9') { + /* We don't accept numbers without leading or trailing digit. */ + return 0; + } + while (buf != end && *buf >= '0' && *buf <= '9') { + if (fraction >= UINT64_MAX / 10) { + if (!ulp_half_error) { + fraction += *buf >= '5'; + ulp_half_error = 1; + } + break; + } + fraction = fraction * 10 + *buf++ - '0'; + --exponent; + } + fraction_exp += (int)(buf - k); + while (buf != end && *buf == '0') { + ++exponent; + ++buf; + } + if (buf != end && *buf >= '1' && *buf <= '9') { + ulp_half_error = 1; + ++buf; + while (buf != end && *buf >= '0' && *buf <= '9') { + ++buf; + } + } + } + /* + * Normalized exponent e.g: 1.23434e3 with fraction = 123434, + * fraction_exp = 5, exponent = 3. + * So value = fraction * 10^(exponent - fraction_exp) + */ + exponent += fraction_exp; + if (buf != end && (*buf | 0x20) == 'e') { + if (end - buf < 2) { + return 0; + } + ++buf; + if (*buf == '+') { + ++buf; + if (buf == end) { + return 0; + } + } else if (*buf == '-') { + esign = 1; + ++buf; + if (buf == end) { + return 0; + } + } + if (*buf < '0' || *buf > '9') { + return 0; + } + ee = *buf++ - '0'; + while (buf != end && *buf >= '0' && *buf <= '9') { + /* + * This test impacts performance and we do not need an + * exact value just one large enough to dominate the fraction_exp. + * Subsequent handling maps large absolute ee to 0 or infinity. + */ + if (ee <= 0x7fff) { + ee = ee * 10 + *buf - '0'; + } + ++buf; + } + } + exponent = exponent + (esign ? -ee : ee); + + /* + * Exponent is now a base 10 normalized exponent so the absolute value + * is less the 10^(exponent + 1) for positive exponents. For + * denormalized doubles (using 11 bit exponent 0 with a fraction + * shiftet down, extra small numbers can be achieved. + * + * https://en.wikipedia.org/wiki/Double-precision_floating-point_format + * + * 10^-324 holds the smallest normalized exponent (but not value) and + * 10^308 holds the largest exponent. Internally our lookup table is + * only safe to use within a range slightly larger than this. + * Externally, a slightly larger/smaller value represents NaNs which + * are technically also possible to store as a number. + * + */ + + /* This also protects strod fallback parsing. */ + if (buf == end) { + return 0; + } + return grisu3_encode_double(mark, buf, sign, fraction, exponent, fraction_exp, ulp_half_error, result); +} + +#endif /* GRISU3_PARSE_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/grisu3_print.h b/Source/library/FBSUtil/flatcc/portable/grisu3_print.h new file mode 100644 index 0000000..fce20f0 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/grisu3_print.h @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com + * Copyright author of MathGeoLib (https://github.com/juj) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. http://www.apache.org/licenses/LICENSE-2.0 + */ + +/* + * Extracted from MathGeoLib. + * + * mikkelfj: + * - Fixed final output when printing single digit negative exponent to + * have leading zero (important for JSON). + * - Changed formatting to prefer 0.012 over 1.2-e-2. + * + * Large portions of the original grisu3.c file has been moved to + * grisu3_math.h, the rest is placed here. + * + * See also comments in grisu3_math.h. + * + * MatGeoLib grisu3.c comment: + * + * This file is part of an implementation of the "grisu3" double to string + * conversion algorithm described in the research paper + * + * "Printing Floating-Point Numbers Quickly And Accurately with Integers" + * by Florian Loitsch, available at + * http://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf + */ + +#ifndef GRISU3_PRINT_H +#define GRISU3_PRINT_H + +#include /* sprintf */ +#include /* assert */ + +#include "grisu3_math.h" + +/* + * The lightweight "portable" C library recognizes grisu3 support if + * included first. + */ +#define grisu3_print_double_is_defined 1 + +/* + * Not sure we have an exact definition, but we get up to 23 + * emperically. There is some math ensuring it does not go awol though, + * like 18 digits + exponent or so. + * This max should be safe size buffer for printing, including zero term. + */ +#define GRISU3_PRINT_MAX 30 + +static int grisu3_round_weed(char *buffer, int len, uint64_t wp_W, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t ulp) +{ + uint64_t wp_Wup = wp_W - ulp; + uint64_t wp_Wdown = wp_W + ulp; + while(rest < wp_Wup && delta - rest >= ten_kappa + && (rest + ten_kappa < wp_Wup || wp_Wup - rest >= rest + ten_kappa - wp_Wup)) + { + --buffer[len-1]; + rest += ten_kappa; + } + if (rest < wp_Wdown && delta - rest >= ten_kappa + && (rest + ten_kappa < wp_Wdown || wp_Wdown - rest > rest + ten_kappa - wp_Wdown)) + return 0; + + return 2*ulp <= rest && rest <= delta - 4*ulp; +} + +static int grisu3_digit_gen(grisu3_diy_fp_t low, grisu3_diy_fp_t w, grisu3_diy_fp_t high, char *buffer, int *length, int *kappa) +{ + uint64_t unit = 1; + grisu3_diy_fp_t too_low = { low.f - unit, low.e }; + grisu3_diy_fp_t too_high = { high.f + unit, high.e }; + grisu3_diy_fp_t unsafe_interval = grisu3_diy_fp_minus(too_high, too_low); + grisu3_diy_fp_t one = { 1ULL << -w.e, w.e }; + uint32_t p1 = (uint32_t)(too_high.f >> -one.e); + uint64_t p2 = too_high.f & (one.f - 1); + uint32_t div; + *kappa = grisu3_largest_pow10(p1, GRISU3_DIY_FP_FRACT_SIZE + one.e, &div); + *length = 0; + + while(*kappa > 0) + { + uint64_t rest; + int digit = p1 / div; + buffer[*length] = (char)('0' + digit); + ++*length; + p1 %= div; + --*kappa; + rest = ((uint64_t)p1 << -one.e) + p2; + if (rest < unsafe_interval.f) return grisu3_round_weed(buffer, *length, grisu3_diy_fp_minus(too_high, w).f, unsafe_interval.f, rest, (uint64_t)div << -one.e, unit); + div /= 10; + } + + for(;;) + { + int digit; + p2 *= 10; + unit *= 10; + unsafe_interval.f *= 10; + /* Integer division by one. */ + digit = (int)(p2 >> -one.e); + buffer[*length] = (char)('0' + digit); + ++*length; + p2 &= one.f - 1; /* Modulo by one. */ + --*kappa; + if (p2 < unsafe_interval.f) return grisu3_round_weed(buffer, *length, grisu3_diy_fp_minus(too_high, w).f * unit, unsafe_interval.f, p2, one.f, unit); + } +} + +static int grisu3(double v, char *buffer, int *length, int *d_exp) +{ + int mk, kappa, success; + grisu3_diy_fp_t dfp = grisu3_cast_diy_fp_from_double(v); + grisu3_diy_fp_t w = grisu3_diy_fp_normalize(dfp); + + /* normalize boundaries */ + grisu3_diy_fp_t t = { (dfp.f << 1) + 1, dfp.e - 1 }; + grisu3_diy_fp_t b_plus = grisu3_diy_fp_normalize(t); + grisu3_diy_fp_t b_minus; + grisu3_diy_fp_t c_mk; /* Cached power of ten: 10^-k */ + uint64_t u64 = grisu3_cast_uint64_from_double(v); + assert(v > 0 && v <= 1.7976931348623157e308); /* Grisu only handles strictly positive finite numbers. */ + if (!(u64 & GRISU3_D64_FRACT_MASK) && (u64 & GRISU3_D64_EXP_MASK) != 0) { b_minus.f = (dfp.f << 2) - 1; b_minus.e = dfp.e - 2;} /* lower boundary is closer? */ + else { b_minus.f = (dfp.f << 1) - 1; b_minus.e = dfp.e - 1; } + b_minus.f = b_minus.f << (b_minus.e - b_plus.e); + b_minus.e = b_plus.e; + + mk = grisu3_diy_fp_cached_pow(GRISU3_MIN_TARGET_EXP - GRISU3_DIY_FP_FRACT_SIZE - w.e, &c_mk); + + w = grisu3_diy_fp_multiply(w, c_mk); + b_minus = grisu3_diy_fp_multiply(b_minus, c_mk); + b_plus = grisu3_diy_fp_multiply(b_plus, c_mk); + + success = grisu3_digit_gen(b_minus, w, b_plus, buffer, length, &kappa); + *d_exp = kappa - mk; + return success; +} + +static int grisu3_i_to_str(int val, char *str) +{ + int len, i; + char *s; + char *begin = str; + if (val < 0) { *str++ = '-'; val = -val; } + s = str; + + for(;;) + { + int ni = val / 10; + int digit = val - ni*10; + *s++ = (char)('0' + digit); + if (ni == 0) + break; + val = ni; + } + *s = '\0'; + len = (int)(s - str); + for(i = 0; i < len/2; ++i) + { + char ch = str[i]; + str[i] = str[len-1-i]; + str[len-1-i] = ch; + } + + return (int)(s - begin); +} + +static int grisu3_print_double(double v, char *dst) +{ + int d_exp, len, success, decimals, i; + uint64_t u64 = grisu3_cast_uint64_from_double(v); + char *s2 = dst; + assert(dst); + + /* Prehandle NaNs */ + if ((u64 << 1) > 0xFFE0000000000000ULL) return sprintf(dst, "NaN(%08X%08X)", (uint32_t)(u64 >> 32), (uint32_t)u64); + /* Prehandle negative values. */ + if ((u64 & GRISU3_D64_SIGN) != 0) { *s2++ = '-'; v = -v; u64 ^= GRISU3_D64_SIGN; } + /* Prehandle zero. */ + if (!u64) { *s2++ = '0'; *s2 = '\0'; return (int)(s2 - dst); } + /* Prehandle infinity. */ + if (u64 == GRISU3_D64_EXP_MASK) { *s2++ = 'i'; *s2++ = 'n'; *s2++ = 'f'; *s2 = '\0'; return (int)(s2 - dst); } + + success = grisu3(v, s2, &len, &d_exp); + /* If grisu3 was not able to convert the number to a string, then use old sprintf (suboptimal). */ + if (!success) return sprintf(s2, "%.17g", v) + (int)(s2 - dst); + + /* We now have an integer string of form "151324135" and a base-10 exponent for that number. */ + /* Next, decide the best presentation for that string by whether to use a decimal point, or the scientific exponent notation 'e'. */ + /* We don't pick the absolute shortest representation, but pick a balance between readability and shortness, e.g. */ + /* 1.545056189557677e-308 could be represented in a shorter form */ + /* 1545056189557677e-323 but that would be somewhat unreadable. */ + decimals = GRISU3_MIN(-d_exp, GRISU3_MAX(1, len-1)); + + /* mikkelfj: + * fix zero prefix .1 => 0.1, important for JSON export. + * prefer unscientific notation at same length: + * -1.2345e-4 over -1.00012345, + * -1.0012345 over -1.2345e-3 + */ + if (d_exp < 0 && (len + d_exp) > -3 && len <= -d_exp) + { + /* mikkelfj: fix zero prefix .1 => 0.1, and short exponents 1.3e-2 => 0.013. */ + memmove(s2 + 2 - d_exp - len, s2, len); + s2[0] = '0'; + s2[1] = '.'; + for (i = 2; i < 2-d_exp-len; ++i) s2[i] = '0'; + len += i; + } + else if (d_exp < 0 && len > 1) /* Add decimal point? */ + { + for(i = 0; i < decimals; ++i) s2[len-i] = s2[len-i-1]; + s2[len++ - decimals] = '.'; + d_exp += decimals; + /* Need scientific notation as well? */ + if (d_exp != 0) { s2[len++] = 'e'; len += grisu3_i_to_str(d_exp, s2+len); } + } + /* Add scientific notation? */ + else if (d_exp < 0 || d_exp > 2) { s2[len++] = 'e'; len += grisu3_i_to_str(d_exp, s2+len); } + /* Add zeroes instead of scientific notation? */ + else if (d_exp > 0) { while(d_exp-- > 0) s2[len++] = '0'; } + s2[len] = '\0'; /* grisu3 doesn't null terminate, so ensure termination. */ + return (int)(s2+len-dst); +} + +#endif /* GRISU3_PRINT_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/paligned_alloc.h b/Source/library/FBSUtil/flatcc/portable/paligned_alloc.h new file mode 100644 index 0000000..ed90c30 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/paligned_alloc.h @@ -0,0 +1,158 @@ +#ifndef PALIGNED_ALLOC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * NOTE: MSVC in general has no aligned alloc function that is + * compatible with free and it is not trivial to implement a version + * which is. Therefore, to remain portable, end user code needs to + * use `aligned_free` which is not part of C11 but defined in this header. + * + * The same issue is present on some Unix systems not providing + * posix_memalign. + * + * For C11 compliant compilers and compilers with posix_memalign, + * it is valid to use free instead of aligned_free with the above + * caveats. + */ + +#include + + +/* + * Define this to see which version is used so the fallback is not + * enganged unnecessarily: + * + * #define PORTABLE_DEBUG_ALIGNED_ALLOC + */ + +#if !defined(PORTABLE_C11_ALIGNED_ALLOC) + +#if defined (__GLIBC__) +#define PORTABLE_C11_ALIGNED_ALLOC 0 +#elif defined (__clang__) +#define PORTABLE_C11_ALIGNED_ALLOC 0 +#elif defined(__IBMC__) +#define PORTABLE_C11_ALIGNED_ALLOC 0 +#elif (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) +#define PORTABLE_C11_ALIGNED_ALLOC 1 +#else +#define PORTABLE_C11_ALIGNED_ALLOC 0 +#endif + +#endif /* PORTABLE_C11_ALIGNED_ALLOC */ + +/* https://linux.die.net/man/3/posix_memalign */ +#if !defined(PORTABLE_POSIX_MEMALIGN) +/* https://forum.kde.org/viewtopic.php?p=66274 */ +#if (defined _GNU_SOURCE) || ((_XOPEN_SOURCE + 0) >= 600) || (_POSIX_C_SOURCE + 0) >= 200112L +#define PORTABLE_POSIX_MEMALIGN 1 +#elif (__clang__) +#define PORTABLE_POSIX_MEMALIGN 1 +#else +#define PORTABLE_POSIX_MEMALIGN 0 +#endif +#endif /* PORTABLE_POSIX_MEMALIGN */ + +/* https://forum.kde.org/viewtopic.php?p=66274 */ +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) +/* C11 or newer */ +#include +#endif + +/* C11 or newer */ +#if !defined(aligned_alloc) && !defined(__aligned_alloc_is_defined) + +#if PORTABLE_C11_ALIGNED_ALLOC +#ifdef PORTABLE_DEBUG_ALIGNED_ALLOC +#error "DEBUG: C11_ALIGNED_ALLOC configured" +#endif +#elif defined(_MSC_VER) + +/* Aligned _aligned_malloc is not compatible with free. */ +#define aligned_alloc(alignment, size) _aligned_malloc(size, alignment) +#define aligned_free(p) _aligned_free(p) +#define __aligned_alloc_is_defined 1 +#define __aligned_free_is_defined 1 + +#elif PORTABLE_POSIX_MEMALIGN + +#if defined(__GNUC__) && __GNUCC__ < 5 +extern int posix_memalign (void **, size_t, size_t); +#endif + +static inline void *__portable_aligned_alloc(size_t alignment, size_t size) +{ + int err; + void *p = 0; + + if (alignment < sizeof(void *)) { + alignment = sizeof(void *); + } + err = posix_memalign(&p, alignment, size); + if (err && p) { + free(p); + p = 0; + } + return p; +} + +#ifdef PORTABLE_DEBUG_ALIGNED_ALLOC +#error "DEBUG: POSIX_MEMALIGN configured" +#endif + +#define aligned_alloc(alignment, size) __portable_aligned_alloc(alignment, size) +#define aligned_free(p) free(p) +#define __aligned_alloc_is_defined 1 +#define __aligned_free_is_defined 1 + +#else + +static inline void *__portable_aligned_alloc(size_t alignment, size_t size) +{ + char *raw; + void *buf; + size_t total_size = (size + alignment - 1 + sizeof(void *)); + + if (alignment < sizeof(void *)) { + alignment = sizeof(void *); + } + raw = (char *)(size_t)malloc(total_size); + buf = raw + alignment - 1 + sizeof(void *); + buf = (void *)(((size_t)buf) & ~(alignment - 1)); + ((void **)buf)[-1] = raw; + return buf; +} + +static inline void __portable_aligned_free(void *p) +{ + char *raw = ((void **)p)[-1]; + + free(raw); +} + +#define aligned_alloc(alignment, size) __portable_aligned_alloc(alignment, size) +#define aligned_free(p) __portable_aligned_free(p) +#define __aligned_alloc_is_defined 1 +#define __aligned_free_is_defined 1 + +#ifdef PORTABLE_DEBUG_ALIGNED_ALLOC +#error "DEBUG: aligned_alloc malloc fallback configured" +#endif + +#endif + +#endif /* aligned_alloc */ + +#if !defined(aligned_free) && !defined(__aligned_free_is_defined) +#define aligned_free(p) free(p) +#define __aligned_free_is_defined 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PALIGNED_ALLOC_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pdiagnostic.h b/Source/library/FBSUtil/flatcc/portable/pdiagnostic.h new file mode 100644 index 0000000..eb66129 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pdiagnostic.h @@ -0,0 +1,61 @@ + /* There is intentionally no include guard in this file. */ + + +/* + * Usage: optionally disable any of these before including. + * + * #define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION + * #define PDIAGNOSTIC_IGNORE_UNUSED_VARIABLE + * #define PDIAGNOSTIC_IGNORE_UNUSED_PARAMETER + * #define PDIAGNOSTIC_IGNORE_UNUSED // all of the above + * + * #include "pdiagnostic.h" + * + * Alternatively use #include "pdiagnostic_push/pop.h" + */ + +#ifdef _MSC_VER +#pragma warning(disable: 4668) /* preprocessor name not defined */ +#endif + +#if defined(_MSC_VER) && !defined(PDIAGNOSTIC_AWARE_MSVC) +#define PDIAGNOSTIC_AWARE_MSVC 1 +#elif defined(__clang__) && !defined(PDIAGNOSTIC_AWARE_CLANG) +#define PDIAGNOSTIC_AWARE_CLANG 1 +/* Can disable some warnings even if push is not available (gcc-4.2 vs gcc-4.7) */ +#elif ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) && \ + !defined(PDIAGNOSTIC_AWARE_GCC) +#define PDIAGNOSTIC_AWARE_GCC 1 +#endif + +#if defined(PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION) || defined(PDIAGNOSTIC_IGNORE_UNUSED) +#if PDIAGNOSTIC_AWARE_CLANG +#pragma clang diagnostic ignored "-Wunused-function" +#elif PDIAGNOSTIC_AWARE_GCC +#pragma GCC diagnostic ignored "-Wunused-function" */ +#endif +#endif +#undef PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION + +#if defined(PDIAGNOSTIC_IGNORE_UNUSED_VARIABLE) || defined(PDIAGNOSTIC_IGNORE_UNUSED) +#if PDIAGNOSTIC_AWARE_MSVC +#pragma warning(disable: 4101) /* unused local variable */ +#elif PDIAGNOSTIC_AWARE_CLANG +#pragma clang diagnostic ignored "-Wunused-variable" +#elif PDIAGNOSTIC_AWARE_GCC +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif +#endif +#undef PDIAGNOSTIC_IGNORE_UNUSED_VARIABLE + +#if defined(PDIAGNOSTIC_IGNORE_UNUSED_PARAMETER) || defined(PDIAGNOSTIC_IGNORE_UNUSED) +#if PDIAGNOSTIC_AWARE_CLANG +#pragma clang diagnostic ignored "-Wunused-parameter" +#elif PDIAGNOSTIC_AWARE_GCC +#pragma GCC diagnostic ignored "-Wunused-parameter" +#endif +#endif +#undef PDIAGNOSTIC_IGNORE_UNUSED_PARAMETER + +#undef PDIAGNOSTIC_IGNORE_UNUSED + diff --git a/Source/library/FBSUtil/flatcc/portable/pdiagnostic_pop.h b/Source/library/FBSUtil/flatcc/portable/pdiagnostic_pop.h new file mode 100644 index 0000000..b83ec4b --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pdiagnostic_pop.h @@ -0,0 +1,15 @@ +#ifndef PDIAGNOSTIC_POP_H +#define PDIAGNOSTIC_POP_H + +#if PDIAGNOSTIC_PUSHED_MSVC +#pragma warning( pop ) +#undef PDIAGNOSTIC_PUSHED_MSVC +#elif PDIAGNOSTIC_PUSHED_CLANG +#pragma clang diagnostic pop +#undef PDIAGNOSTIC_PUSHED_CLANG +#elif PDIAGNOSTIC_PUSHED_GCC +#pragma GCC diagnostic pop +#undef PDIAGNOSTIC_PUSHED_GCC +#endif + +#endif /* PDIAGNOSTIC_POP_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pdiagnostic_push.h b/Source/library/FBSUtil/flatcc/portable/pdiagnostic_push.h new file mode 100644 index 0000000..2adc231 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pdiagnostic_push.h @@ -0,0 +1,46 @@ +#ifndef PDIAGNOSTIC_PUSH_H +#define PDIAGNOSTIC_PUSH_H + +/* + * See also comment in "pdiagnostic.h" + * + * e.g. + * #define PDIAGNOSTIC_IGNORE_USED_FUNCTION + * #define PDIAGNOSTIC_IGNORE_USED_VARIABLE + * #include "pdiagnostic_push" + * ... + * #include "pdiagnostic_pop.h" + * + * + * or if push pop isn't desired: + * #define PDIAGNOSTIC_IGNORE_USED_FUNCTION + * #define PDIAGNOSTIC_IGNORE_USED_VARIABLE + * #include "pdiagnostic.h" + * ... + * + * + * + * Some if these warnings cannot be ignored + * at the #pragma level, but might in the future. + * Use compiler switches like -Wno-unused-function + * to work around this. + */ + +#if defined(_MSC_VER) +#pragma warning( push ) +#define PDIAGNOSTIC_PUSHED_MSVC 1 +#elif defined(__clang__) +#pragma clang diagnostic push +#define PDIAGNOSTIC_PUSHED_CLANG 1 +#elif ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic push +#define PDIAGNOSTIC_PUSHED_GCC 1 +#endif + +#endif /* PDIAGNOSTIC_PUSH_H */ + +/* + * We cannot handle nested push, but we can add to the parent context + * so keep this outside the header include guard. + */ +#include "pdiagnostic.h" diff --git a/Source/library/FBSUtil/flatcc/portable/pendian.h b/Source/library/FBSUtil/flatcc/portable/pendian.h new file mode 100644 index 0000000..d5bdc6f --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pendian.h @@ -0,0 +1,177 @@ +#ifndef PENDIAN_H +#define PENDIAN_H + +/* + * Defines platform optimized (as per linux + * + * le16toh, le32to, le64toh, be16toh, be32toh, be64toh + * htole16, htole32, htole64, htobe16, htobe32, htobe64 + * + * Falls back to auto-detect endian conversion which is also fast + * if fast byteswap operation was detected. + * + * Also defines platform optimized: + * + * bswap16, bswap32, bswap64, + * + * with fall-back to shift-or implementation. + * + * For convenience also defines: + * + * le8to, be8toh, htole8, htobe8 + * bswap8 + * + * The convience functions makes is simpler to define conversion macros + * based on type size. + * + * NOTE: this implementation expects arguments with no side-effects and + * with appropriately sized unsigned arguments. These are expected to be + * used with typesafe wrappers. + */ + +#ifndef UINT8_MAX +#include "pstdint.h" +#endif + +#include "pendian_detect.h" + +#if defined(_MSC_VER) +#if _MSC_VER >= 1300 +#include +#define bswap16 _byteswap_ushort +#define bswap32 _byteswap_ulong +#define bswap64 _byteswap_uint64 +#endif +#elif defined(__clang__) +#if __has_builtin(__builtin_bswap16) +#define bswap16 __builtin_bswap16 +#endif +#if __has_builtin(__builtin_bswap32) +#define bswap32 __builtin_bswap32 +#endif +#if __has_builtin(__builtin_bswap64) +#define bswap64 __builtin_bswap64 +#endif +#elif defined(__OpenBSD__) +#include +#define bswap16 swap16 +#define bswap32 swap32 +#define bswap64 swap64 +#elif defined(__GNUC__) /* Supported since at least GCC 4.4 */ +#define bswap32 __builtin_bswap32 +#define bswap64 __builtin_bswap64 +#endif + +#ifndef bswap16 +#define bswap16(v) \ + (((uint16_t)(v) << 8) | ((uint16_t)(v) >> 8)) +#endif + +#ifndef bswap32 +#define bswap32(v) \ + ((((uint32_t)(v) << 24)) \ + | (((uint32_t)(v) << 8) & UINT32_C(0x00FF0000)) \ + | (((uint32_t)(v) >> 8) & UINT32_C(0x0000FF00)) \ + | (((uint32_t)(v) >> 24))) +#endif + +#ifndef bswap64 +#define bswap64(v) \ + ((((uint64_t)(v) << 56)) \ + | (((uint64_t)(v) << 40) & UINT64_C(0x00FF000000000000)) \ + | (((uint64_t)(v) << 24) & UINT64_C(0x0000FF0000000000)) \ + | (((uint64_t)(v) << 8) & UINT64_C(0x000000FF00000000)) \ + | (((uint64_t)(v) >> 8) & UINT64_C(0x00000000FF000000)) \ + | (((uint64_t)(v) >> 24) & UINT64_C(0x0000000000FF0000)) \ + | (((uint64_t)(v) >> 40) & UINT64_C(0x000000000000FF00)) \ + | (((uint64_t)(v) >> 56))) +#endif + +#ifndef bswap8 +#define bswap8(v) ((uint8_t)(v)) +#endif + +#if !defined(le16toh) && defined(letoh16) +#define le16toh letoh16 +#define le32toh letoh32 +#define le64toh letoh64 +#endif + +#if !defined(be16toh) && defined(betoh16) +#define be16toh betoh16 +#define be32toh betoh32 +#define be64toh betoh64 +#endif + +/* Assume it goes for all. */ +#if !defined(le16toh) + +#if defined(__LITTLE_ENDIAN__) + +#define le16toh(v) (v) +#define le32toh(v) (v) +#define le64toh(v) (v) + +#define htole16(v) (v) +#define htole32(v) (v) +#define htole64(v) (v) + +#define be16toh(v) bswap16(v) +#define be32toh(v) bswap32(v) +#define be64toh(v) bswap64(v) + +#define htobe16(v) bswap16(v) +#define htobe32(v) bswap32(v) +#define htobe64(v) bswap64(v) + +#elif defined(__BIG_ENDIAN__) + +#define le16toh(v) bswap16(v) +#define le32toh(v) bswap32(v) +#define le64toh(v) bswap64(v) + +#define htole16(v) bswap16(v) +#define htole32(v) bswap32(v) +#define htole64(v) bswap64(v) + +#define be16toh(v) (v) +#define be32toh(v) (v) +#define be64toh(v) (v) + +#define htobe16(v) (v) +#define htobe32(v) (v) +#define htobe64(v) (v) + +#else + +static const int __pendian_test = 1; + +#define le16toh(v) (*(char *)&__pendian_test ? (v) : bswap16(v)) +#define le32toh(v) (*(char *)&__pendian_test ? (v) : bswap32(v)) +#define le64toh(v) (*(char *)&__pendian_test ? (v) : bswap64(v)) + +#define htole16(v) (*(char *)&__pendian_test ? (v) : bswap16(v)) +#define htole32(v) (*(char *)&__pendian_test ? (v) : bswap32(v)) +#define htole64(v) (*(char *)&__pendian_test ? (v) : bswap64(v)) + +#define be16toh(v) (*(char *)&__pendian_test ? bswap16(v) : (v)) +#define be32toh(v) (*(char *)&__pendian_test ? bswap32(v) : (v)) +#define be64toh(v) (*(char *)&__pendian_test ? bswap64(v) : (v)) + +#define htobe16(v) (*(char *)&__pendian_test ? bswap16(v) : (v)) +#define htobe32(v) (*(char *)&__pendian_test ? bswap32(v) : (v)) +#define htobe64(v) (*(char *)&__pendian_test ? bswap64(v) : (v)) + +#endif + +#endif /* le16toh */ + +/* Helpers not part of Linux */ +#if !defined(le8toh) +#define le8toh(n) (n) +#define htole8(n) (n) +#define be8toh(n) (n) +#define htobe8(n) (n) +#endif + +#endif /* PENDIAN_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pendian_detect.h b/Source/library/FBSUtil/flatcc/portable/pendian_detect.h new file mode 100644 index 0000000..fdf304e --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pendian_detect.h @@ -0,0 +1,110 @@ +/* + * Uses various known flags to decide endianness and defines: + * + * __LITTLE_ENDIAN__ or __BIG_ENDIAN__ if not already defined + * + * and also defines + * + * __BYTE_ORDER__ to either __ORDER_LITTLE_ENDIAN__ or + * __ORDER_BIG_ENDIAN__ if not already defined + * + * If none of these could be set, __UNKNOWN_ENDIAN__ is defined, + * which is not a known flag. If __BYTE_ORDER__ is defined but + * not big or little endian, __UNKNOWN_ENDIAN__ is also defined. + * + * Note: Some systems define __BYTE_ORDER without __ at the end - + * we detect this and map it to __BYTE_ORDER__. + */ + +#ifndef PENDIAN_DETECT +#define PENDIAN_DETECT + +#ifndef __ORDER_LITTLE_ENDIAN__ +#define __ORDER_LITTLE_ENDIAN__ 1234 +#endif + +#ifndef __ORDER_BIG_ENDIAN__ +#define __ORDER_BIG_ENDIAN__ 4321 +#endif + +#ifdef __BYTE_ORDER__ + +#if defined(__LITTLE_ENDIAN__) && __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error __LITTLE_ENDIAN__ inconsistent with __BYTE_ORDER__ +#endif + +#if defined(__BIG_ENDIAN__) && __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ +#error __BIG_ENDIAN__ inconsistent with __BYTE_ORDER__ +#endif + +#else /* __BYTE_ORDER__ */ + + +#if \ + defined(__LITTLE_ENDIAN__) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || \ + defined(__AARCH64EL__) || \ + (defined(_MSC_VER) && defined(_M_ARM)) || \ + defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ + defined(_M_X64) || defined(_M_IX86) || defined(_M_I86) || \ + defined(__i386__) || defined(__alpha__) || \ + defined(__ia64) || defined(__ia64__) || \ + defined(_M_IA64) || defined(_M_ALPHA) || \ + defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(__bfin__) + +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ + +#endif + +#if \ + defined (__BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __ORDER_BIG_ENDIAN) || \ + defined(__ARMEB__) || defined(THUMBEB__) || defined (__AARCH64EB__) || \ + defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__) || \ + defined(__sparc) || defined(__sparc__) || \ + defined(_POWER) || defined(__powerpc__) || defined(__ppc__) || \ + defined(__hpux) || defined(__hppa) || defined(__s390__) + +#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__ + +#endif + +#endif /* __BYTE_ORDER__ */ + +#ifdef __BYTE_ORDER__ + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + +#ifndef __LITTLE_ENDIAN__ +#define __LITTLE_ENDIAN__ 1 +#endif + +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + +#ifndef __BIG_ENDIAN__ +#define __BIG_ENDIAN__ 1 +#endif + +#else + +/* + * Custom extension - we only define __BYTE_ORDER if known big or little. + * User code that understands __BYTE_ORDER__ may also assume unkown if + * it is not defined by know - this will allow other endian formats than + * big or little when supported by compiler. + */ +#ifndef __UNKNOWN_ENDIAN__ +#define __UNKNOWN_ENDIAN__ 1 +#endif + +#endif +#endif /* __BYTE_ORDER__ */ + +#if defined(__LITTLE_ENDIAN__) && defined(__BIG_ENDIAN__) +#error conflicting definitions of __LITTLE_ENDIAN__ and __BIG_ENDIAN__ +#endif + +#endif /* PENDIAN_DETECT */ diff --git a/Source/library/FBSUtil/flatcc/portable/pinline.h b/Source/library/FBSUtil/flatcc/portable/pinline.h new file mode 100644 index 0000000..f4f8f27 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pinline.h @@ -0,0 +1,19 @@ +#ifndef PINLINE_H +#define PINLINE_H + +#ifndef __cplusplus + +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +/* C99 or newer */ +#elif _MSC_VER >= 1500 /* MSVC 9 or newer */ +#undef inline +#define inline __inline +#elif __GNUC__ >= 3 /* GCC 3 or newer */ +#define inline __inline +#else /* Unknown or ancient */ +#define inline +#endif + +#endif /* __cplusplus */ + +#endif /* PINLINE_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pinttypes.h b/Source/library/FBSUtil/flatcc/portable/pinttypes.h new file mode 100644 index 0000000..5833cec --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pinttypes.h @@ -0,0 +1,69 @@ +#ifndef PINTTYPES_H +#define PINTTYPES_H + +#if (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +/* C99 or newer */ +#include +#else + +/* + * This is not a complete implementation of , just the most + * useful printf modifiers. + */ + +#include "pstdint.h" + +#ifndef PRINTF_INT64_MODIFIER +#error "please define PRINTF_INT64_MODIFIER" +#endif + +#ifndef PRId64 +#define PRId64 PRINTF_INT64_MODIFIER "d" +#define PRIu64 PRINTF_INT64_MODIFIER "u" +#define PRIx64 PRINTF_INT64_MODIFIER "x" +#endif + +#ifndef PRINTF_INT32_MODIFIER +#define PRINTF_INT32_MODIFIER "l" +#endif + +#ifndef PRId32 +#define PRId32 PRINTF_INT32_MODIFIER "d" +#define PRIu32 PRINTF_INT32_MODIFIER "u" +#define PRIx32 PRINTF_INT32_MODIFIER "x" +#endif + +#ifndef PRINTF_INT16_MODIFIER +#define PRINTF_INT16_MODIFIER "h" +#endif + +#ifndef PRId16 +#define PRId16 PRINTF_INT16_MODIFIER "d" +#define PRIu16 PRINTF_INT16_MODIFIER "u" +#define PRIx16 PRINTF_INT16_MODIFIER "x" +#endif + +# endif /* __STDC__ */ + +#ifdef PORTABLE_USE_DEPRECATED_INTTYPES +#ifndef PRIszu +#ifdef _MSC_VER + #define PRIszd "Id" + #define PRIszu "Iu" + #define PRIszx "Ix" + #define PRIpdu "Iu" + #define PRIpdd "Id" + #define PRIpdx "Ix" +#else + #define PRIszd "zd" + #define PRIszu "zu" + #define PRIszx "zx" + #define PRIpdd "td" + #define PRIpdu "tu" + #define PRIpdx "tx" +#endif +#endif +#endif + + +#endif /* PINTTYPES */ diff --git a/Source/library/FBSUtil/flatcc/portable/portable.h b/Source/library/FBSUtil/flatcc/portable/portable.h new file mode 100644 index 0000000..e8f79cc --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/portable.h @@ -0,0 +1,17 @@ +#ifndef PORTABLE_H +#define PORTABLE_H + +#include "pversion.h" +#include "pwarnings.h" + +/* Featutures that ought to be supported by C11, but some aren't. */ +#include "pinttypes.h" +#include "pstdalign.h" +#include "pinline.h" +#include "pstatic_assert.h" + +/* These are not supported by C11 and are general platform abstractions. */ +#include "pendian.h" +#include "punaligned.h" + +#endif /* PORTABLE_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pparsefp.h b/Source/library/FBSUtil/flatcc/portable/pparsefp.h new file mode 100644 index 0000000..d4a3da9 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pparsefp.h @@ -0,0 +1,113 @@ +#ifndef PPARSEFP_H +#define PPARSEFP_H + +/* + * Parses a float or double number and returns the length parsed if + * successful. The length argument is of limited value due to dependency + * on `strtod` - buf[len] must be accessible and must not be part of + * a valid number, including hex float numbers.. + * + * Unlike strtod, whitespace is not parsed. + * + * May return: + * - null on error, + * - buffer start if first character does not start a number, + * - or end of parse on success. + * + */ + +#define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION +#include "pdiagnostic_push.h" + +/* + * isinf is needed in order to stay compatible with strtod's + * over/underflow handling but isinf has some portability issues. + * + * Use the parse_double/float_is_range_error instead of isinf directly. + * This ensures optimizations can be added when not using strtod. + * + * On gcc, clang and msvc we can use isinf or equivalent directly. + * Other compilers such as xlc may require linking with -lm which may not + * be convienent so a default isinf is provided. If isinf is available + * and there is a noticable performance issue, define + * `PORTABLE_USE_ISINF`. + */ +#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) || defined(PORTABLE_USE_ISINF) +#include +#if defined(_MSC_VER) && !defined(isinf) +#include +#define isnan _isnan +#define isinf(x) (!_finite(x)) +#endif +#define parse_double_isinf isinf +#define parse_float_isinf isinf +#else + +#ifndef UINT8_MAX +#include +#endif + +/* Avoid linking with libmath but depends on float/double being IEEE754 */ +static inline int parse_double_isinf(double x) +{ + union { uint64_t u64; double f64; } v; + v.f64 = x; + return (v.u64 & 0x7fffffff00000000ULL) == 0x7ff0000000000000ULL; +} + +static inline int parse_float_isinf(float x) +{ + union { uint32_t u32; float f32; } v; + v.f32 = x; + return (v.u32 & 0x7fffffff) == 0x7f800000; +} +#endif + +/* Returns 0 when in range, 1 on overflow, and -1 on underflow. */ +static inline int parse_double_is_range_error(double x) +{ + return parse_double_isinf(x) ? (x < 0.0 ? -1 : 1) : 0; +} + +static inline int parse_float_is_range_error(float x) +{ + return parse_float_isinf(x) ? (x < 0.0f ? -1 : 1) : 0; +} + +#ifndef PORTABLE_USE_GRISU3 +#define PORTABLE_USE_GRISU3 1 +#endif + +#if PORTABLE_USE_GRISU3 +#include "grisu3_parse.h" +#endif + +#ifdef grisu3_parse_double_is_defined +static inline const char *parse_double(const char *buf, int len, double *result) +{ + return grisu3_parse_double(buf, len, result); +} +#else +#include +static inline const char *parse_double(const char *buf, int len, double *result) +{ + char *end; + + (void)len; + *result = strtod(buf, &end); + return end; +} +#endif + +static inline const char *parse_float(const char *buf, int len, float *result) +{ + const char *end; + double v; + + end = parse_double(buf, len, &v); + *result = (float)v; + return end; +} + +#include "pdiagnostic_pop.h" +#endif /* PPARSEFP_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pparseint.h b/Source/library/FBSUtil/flatcc/portable/pparseint.h new file mode 100644 index 0000000..6796c90 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pparseint.h @@ -0,0 +1,366 @@ +#ifndef PPARSEINT_H +#define PPARSEINT_H + +/* + * Type specific integer parsers: + * + * const char * + * parse_(const char *buf, int len, *value, int *status); + * + * parse_uint64, parse_int64 + * parse_uint32, parse_int32 + * parse_uint16, parse_int16 + * parse_uint8, parse_int8 + * parse_ushort, parse_short + * parse_uint, parse_int + * parse_ulong, parse_long + * + * Leading space must be stripped in advance. Status argument can be + * null. + * + * Returns pointer to end of match and a non-negative status code + * on succcess (0 for unsigned, 1 for signed): + * + * PARSE_INTEGER_UNSIGNED + * PARSE_INTEGER_SIGNED + * + * Returns null with a negative status code and unmodified value on + * invalid integer formats: + * + * PARSE_INTEGER_OVERFLOW + * PARSE_INTEGER_UNDERFLOW + * PARSE_INTEGER_INVALID + * + * Returns input buffer with negative status code and unmodified value + * if first character does not start an integer (not a sign or a digit). + * + * PARSE_INTEGER_UNMATCHED + * PARSE_INTEGER_END + * + * The signed parsers only works with two's complement architectures. + * + * Note: the corresponding parse_float and parse_double parsers do not + * have a status argument because +/-Inf and NaN are conventionally used + * for this. + */ + +#include "limits.h" +#ifndef UINT8_MAX +#include +#endif + +#define PARSE_INTEGER_UNSIGNED 0 +#define PARSE_INTEGER_SIGNED 1 +#define PARSE_INTEGER_OVERFLOW -1 +#define PARSE_INTEGER_UNDERFLOW -2 +#define PARSE_INTEGER_INVALID -3 +#define PARSE_INTEGER_UNMATCHED -4 +#define PARSE_INTEGER_END -5 + +/* + * Generic integer parser that holds 64-bit unsigned values and stores + * sign separately. Leading space is not valid. + * + * Note: this function differs from the type specific parsers like + * parse_int64 by not negating the value when there is a sign. It + * differs from parse_uint64 by being able to return a negative + * UINT64_MAX successfully. + * + * This parser is used by all type specific integer parsers. + * + * Status argument can be null. + */ +static const char *parse_integer(const char *buf, int len, uint64_t *value, int *status) +{ + uint64_t x0, x = 0; + const char *k, *end = buf + len; + int sign, status_; + + if (!status) { + status = &status_; + } + if (buf == end) { + *status = PARSE_INTEGER_END; + return buf; + } + k = buf; + sign = *buf == '-'; + buf += sign; + while (buf != end && *buf >= '0' && *buf <= '9') { + x0 = x; + x = x * 10 + *buf - '0'; + if (x0 > x) { + *status = sign ? PARSE_INTEGER_UNDERFLOW : PARSE_INTEGER_OVERFLOW; + return 0; + } + ++buf; + } + if (buf == k) { + /* No number was matched, but it isn't an invalid number either. */ + *status = PARSE_INTEGER_UNMATCHED; + return buf; + } + if (buf == k + sign) { + *status = PARSE_INTEGER_INVALID; + return 0; + } + if (buf != end) + switch (*buf) { + case 'e': case 'E': case '.': case 'p': case 'P': + *status = PARSE_INTEGER_INVALID; + return 0; + } + *value = x; + *status = sign; + return buf; +} + +/* + * Parse hex values like 0xff, -0xff, 0XdeAdBeaf42, cannot be trailed by '.', 'p', or 'P'. + * Overflows if string is more than 16 valid hex digits. Otherwise similar to parse_integer. + */ +static const char *parse_hex_integer(const char *buf, int len, uint64_t *value, int *status) +{ + uint64_t x = 0; + const char *k, *k2, *end = buf + len; + int sign, status_; + unsigned char c; + + if (!status) { + status = &status_; + } + if (buf == end) { + *status = PARSE_INTEGER_END; + return buf; + } + sign = *buf == '-'; + buf += sign; + if (end - buf < 2 || buf[0] != '0' || (buf[1] | 0x20) != 'x') { + *status = PARSE_INTEGER_UNMATCHED; + return buf - sign; + } + buf += 2; + k = buf; + k2 = end; + if (end - buf > 16) { + k2 = buf + 16; + } + while (buf != k2) { + c = *buf; + if (c >= '0' && c <= '9') { + x = x * 16 + c - '0'; + } else { + /* Lower case. */ + c |= 0x20; + if (c >= 'a' && c <= 'f') { + x = x * 16 + c - 'a' + 10; + } else { + break; + } + } + ++buf; + } + if (buf == k) { + if (sign) { + *status = PARSE_INTEGER_INVALID; + return 0; + } else { + /* No number was matched, but it isn't an invalid number either. */ + *status = PARSE_INTEGER_UNMATCHED; + return buf; + } + } + if (buf == end) { + goto done; + } + c = *buf; + if (buf == k2) { + if (c >= '0' && c <= '9') { + *status = sign ? PARSE_INTEGER_UNDERFLOW : PARSE_INTEGER_OVERFLOW; + return 0; + } + c |= 0x20; + if (c >= 'a' && c <= 'f') { + *status = sign ? PARSE_INTEGER_UNDERFLOW : PARSE_INTEGER_OVERFLOW; + return 0; + } + } + switch (c) { + case '.': case 'p': case 'P': + *status = PARSE_INTEGER_INVALID; + return 0; + } +done: + *value = x; + *status = sign; + return buf; +} + + +#define __portable_define_parse_unsigned(NAME, TYPE, LIMIT) \ +static inline const char *parse_ ## NAME \ + (const char *buf, int len, TYPE *value, int *status) \ +{ \ + int status_ = 0; \ + uint64_t x; \ + \ + if (!status) { \ + status = &status_; \ + } \ + buf = parse_integer(buf, len, &x, status); \ + switch (*status) { \ + case PARSE_INTEGER_UNSIGNED: \ + if (x <= LIMIT) { \ + *value = (TYPE)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_OVERFLOW; \ + return 0; \ + case PARSE_INTEGER_SIGNED: \ + *status = PARSE_INTEGER_UNDERFLOW; \ + return 0; \ + default: \ + return buf; \ + } \ +} + +#define __portable_define_parse_hex_unsigned(NAME, TYPE, LIMIT) \ +static inline const char *parse_hex_ ## NAME \ + (const char *buf, int len, TYPE *value, int *status) \ +{ \ + int status_ = 0; \ + uint64_t x; \ + \ + if (!status) { \ + status = &status_; \ + } \ + buf = parse_hex_integer(buf, len, &x, status); \ + switch (*status) { \ + case PARSE_INTEGER_UNSIGNED: \ + if (x <= LIMIT) { \ + *value = (TYPE)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_OVERFLOW; \ + return 0; \ + case PARSE_INTEGER_SIGNED: \ + *status = PARSE_INTEGER_UNDERFLOW; \ + return 0; \ + default: \ + return buf; \ + } \ +} + +/* This assumes two's complement. */ +#define __portable_define_parse_signed(NAME, TYPE, LIMIT) \ +static inline const char *parse_ ## NAME \ + (const char *buf, int len, TYPE *value, int *status) \ +{ \ + int status_ = 0; \ + uint64_t x; \ + \ + if (!status) { \ + status = &status_; \ + } \ + buf = parse_integer(buf, len, &x, status); \ + switch (*status) { \ + case PARSE_INTEGER_UNSIGNED: \ + if (x <= LIMIT) { \ + *value = (TYPE)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_OVERFLOW; \ + return 0; \ + case PARSE_INTEGER_SIGNED: \ + if (x <= (uint64_t)(LIMIT) + 1) { \ + *value = (TYPE)-(int64_t)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_UNDERFLOW; \ + return 0; \ + default: \ + return buf; \ + } \ +} + +/* This assumes two's complement. */ +#define __portable_define_parse_hex_signed(NAME, TYPE, LIMIT) \ +static inline const char *parse_hex_ ## NAME \ + (const char *buf, int len, TYPE *value, int *status) \ +{ \ + int status_ = 0; \ + uint64_t x; \ + \ + if (!status) { \ + status = &status_; \ + } \ + buf = parse_hex_integer(buf, len, &x, status); \ + switch (*status) { \ + case PARSE_INTEGER_UNSIGNED: \ + if (x <= LIMIT) { \ + *value = (TYPE)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_OVERFLOW; \ + return 0; \ + case PARSE_INTEGER_SIGNED: \ + if (x <= (uint64_t)(LIMIT) + 1) { \ + *value = (TYPE)-(int64_t)x; \ + return buf; \ + } \ + *status = PARSE_INTEGER_UNDERFLOW; \ + return 0; \ + default: \ + return buf; \ + } \ +} + +static inline const char *parse_uint64(const char *buf, int len, uint64_t *value, int *status) +{ + buf = parse_integer(buf, len, value, status); + if (*status == PARSE_INTEGER_SIGNED) { + *status = PARSE_INTEGER_UNDERFLOW; + return 0; + } + return buf; +} + +static inline const char *parse_hex_uint64(const char *buf, int len, uint64_t *value, int *status) +{ + buf = parse_hex_integer(buf, len, value, status); + if (*status == PARSE_INTEGER_SIGNED) { + *status = PARSE_INTEGER_UNDERFLOW; + return 0; + } + return buf; +} + +__portable_define_parse_signed(int64, int64_t, INT64_MAX) +__portable_define_parse_signed(int32, int32_t, INT32_MAX) +__portable_define_parse_unsigned(uint16, uint16_t, UINT16_MAX) +__portable_define_parse_signed(int16, int16_t, INT16_MAX) +__portable_define_parse_unsigned(uint8, uint8_t, UINT8_MAX) +__portable_define_parse_signed(int8, int8_t, INT8_MAX) + +__portable_define_parse_hex_signed(int64, int64_t, INT64_MAX) +__portable_define_parse_hex_signed(int32, int32_t, INT32_MAX) +__portable_define_parse_hex_unsigned(uint16, uint16_t, UINT16_MAX) +__portable_define_parse_hex_signed(int16, int16_t, INT16_MAX) +__portable_define_parse_hex_unsigned(uint8, uint8_t, UINT8_MAX) +__portable_define_parse_hex_signed(int8, int8_t, INT8_MAX) + +__portable_define_parse_unsigned(ushort, unsigned short, USHRT_MAX) +__portable_define_parse_signed(short, short, SHRT_MAX) +__portable_define_parse_unsigned(uint, unsigned int, UINT_MAX) +__portable_define_parse_signed(int, int, INT_MAX) +__portable_define_parse_unsigned(ulong, unsigned long, ULONG_MAX) +__portable_define_parse_signed(long, unsigned long, LONG_MAX) + +__portable_define_parse_hex_unsigned(ushort, unsigned short, USHRT_MAX) +__portable_define_parse_hex_signed(short, short, SHRT_MAX) +__portable_define_parse_hex_unsigned(uint, unsigned int, UINT_MAX) +__portable_define_parse_hex_signed(int, int, INT_MAX) +__portable_define_parse_hex_unsigned(ulong, unsigned long, ULONG_MAX) +__portable_define_parse_hex_signed(long, unsigned long, LONG_MAX) + +#endif /* PPARSEINT_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pprintfp.h b/Source/library/FBSUtil/flatcc/portable/pprintfp.h new file mode 100644 index 0000000..76e0cbe --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pprintfp.h @@ -0,0 +1,30 @@ +#ifndef PPRINTFP_H +#define PPRINTFP_H + +#define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION +#include "pdiagnostic_push.h" + +#ifndef PORTABLE_USE_GRISU3 +#define PORTABLE_USE_GRISU3 1 +#endif + + +#if PORTABLE_USE_GRISU3 +#include "grisu3_print.h" +#endif + +#ifdef grisu3_print_double_is_defined +/* Currently there is not special support for floats. */ +#define print_float(n, p) grisu3_print_double((float)(n), (p)) +#define print_double(n, p) grisu3_print_double((double)(n), (p)) +#else +#include +#define print_float(n, p) sprintf(p, "%.9g", (float)(n)) +#define print_double(n, p) sprintf(p, "%.17g", (double)(n)) +#endif + +#define print_hex_float(n, p) sprintf(p, "%a", (float)(n)) +#define print_hex_double(n, p) sprintf(p, "%a", (double)(n)) + +#include "pdiagnostic_pop.h" +#endif /* PPRINTFP_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pprintint.h b/Source/library/FBSUtil/flatcc/portable/pprintint.h new file mode 100644 index 0000000..38e6d0c --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pprintint.h @@ -0,0 +1,598 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2016 Mikkel F. Jørgensen, dvide.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * Fast printing of (u)int8/16/32/64_t, (u)int, (u)long. + * + * Functions take for the + * + * int print_(type value, char *buf); + * + * and returns number of characters printed, excluding trailing '\0' + * which is also printed. Prints at most 21 characters including zero- + * termination. + * + * The function `print_bool` is a bit different - it simply prints "true\0" for + * non-zero integers, and "false\0" otherwise. + * + * The general algorithm is in-place formatting using binary search log10 + * followed by duff device loop unrolling div / 100 stages. + * + * The simpler post copy algorithm also provided for fmt_(u)int uses a + * temp buffer and loops over div/100 and post copy to target buffer. + * + * + * Benchmarks on core-i7, 2.2GHz, 64-bit clang/OS-X -O2: + * + * print_int64: avg 15ns for values between INT64_MIN + (10^7/2 .. 10^7/2) + * print_int64: avg 11ns for values between 10^9 + (0..10,000,000). + * print_int32: avg 7ns for values cast from INT64_MIN + (10^7/2 .. 10^7/2) + * print_int32: avg 7ns for values between 10^9 + (0..10,000,000). + * print_int64: avg 13ns for values between 10^16 + (0..10,000,000). + * print_int64: avg 5ns for values between 0 and 10,000,000. + * print_int32: avg 5ns for values between 0 and 10,000,000. + * print_int16: avg 10ns for values cast from 0 and 10,000,000. + * print_int8: avg 4ns for values cast from 0 and 10,000,000. + * + * Post copy algorithm: + * print_int: avg 12ns for values between INT64_MIN + (10^7/2 .. 10^7/2) + * print_int: avg 14ns for values between 10^9 + (0..10,000,000). + * print_long: avg 29ns for values between INT64_MIN + (10^7/2 .. 10^7/2) + * + * The post copy algorithm is nearly half as fast as the in-place + * algorithm, but can also be faster occasionally - possibly because the + * optimizer being able to skip the copy step. + */ + +#ifndef PPRINTINT_H +#define PPRINTINT_H + +#ifndef UINT8_MAX +#include +#endif + +#define PDIAGNOSTIC_IGNORE_UNUSED_FUNCTION +#include "pdiagnostic_push.h" + +static int print_bool(int n, char *p); + +static int print_uint8(uint8_t n, char *p); +static int print_uint16(uint16_t n, char *p); +static int print_uint32(uint32_t n, char *p); +static int print_uint64(uint64_t n, char *p); +static int print_int8(int8_t n, char *p); +static int print_int16(int16_t n, char *p); +static int print_int32(int32_t n, char *p); +static int print_int64(int64_t n, char *p); + +/* + * Uses slightly slower, but more compact alogrithm + * that is not hardcoded to implementation size. + * Other types may be defined using macros below. + */ +static int print_ulong(unsigned long n, char *p); +static int print_uint(unsigned int n, char *p); +static int print_int(int n, char *p); +static int print_long(long n, char *p); + + +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) +#define __print_unaligned_copy_16(p, q) (*(uint16_t*)(p) = *(uint16_t*)(q)) +#else +#define __print_unaligned_copy_16(p, q) \ + ((((uint8_t*)(p))[0] = ((uint8_t*)(q))[0]), \ + (((uint8_t*)(p))[1] = ((uint8_t*)(q))[1])) +#endif + +static const char __print_digit_pairs[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define __print_stage() \ + p -= 2; \ + dp = __print_digit_pairs + (n % 100) * 2; \ + n /= 100; \ + __print_unaligned_copy_16(p, dp); + +#define __print_long_stage() \ + __print_stage() \ + __print_stage() + +#define __print_short_stage() \ + *--p = (n % 10) + '0'; \ + n /= 10; + +static int print_bool(int n, char *buf) +{ + if (n) { + memcpy(buf, "true\0", 5); + return 4; + } else { + memcpy(buf, "false\0", 6); + return 5; + } +} + +static int print_uint8(uint8_t n, char *p) +{ + const char *dp; + + if (n >= 100) { + p += 3; + *p = '\0'; + __print_stage(); + p[-1] = n + '0'; + return 3; + } + if (n >= 10) { + p += 2; + *p = '\0'; + __print_stage(); + return 2; + } + p[1] = '\0'; + p[0] = n + '0'; + return 1; +} + +static int print_uint16(uint16_t n, char *p) +{ + int k = 0; + const char *dp; + + if (n >= 1000) { + if(n >= 10000) { + k = 5; + } else { + k = 4; + } + } else { + if(n >= 100) { + k = 3; + } else if(n >= 10) { + k = 2; + } else { + k = 1; + } + } + p += k; + *p = '\0'; + if (k & 1) { + switch (k) { + case 5: + __print_stage(); + case 3: + __print_stage(); + case 1: + p[-1] = n + '0'; + } + } else { + switch (k) { + case 4: + __print_stage(); + case 2: + __print_stage(); + } + } + return k; +} + +static int print_uint32(uint32_t n, char *p) +{ + int k = 0; + const char *dp; + + if(n >= 10000UL) { + if(n >= 10000000UL) { + if(n >= 1000000000UL) { + k = 10; + } else if(n >= 100000000UL) { + k = 9; + } else { + k = 8; + } + } else { + if(n >= 1000000UL) { + k = 7; + } else if(n >= 100000UL) { + k = 6; + } else { + k = 5; + } + } + } else { + if(n >= 100UL) { + if(n >= 1000UL) { + k = 4; + } else { + k = 3; + } + } else { + if(n >= 10UL) { + k = 2; + } else { + k = 1UL; + } + } + } + p += k; + *p = '\0'; + if (k & 1) { + switch (k) { + case 9: + __print_stage(); + case 7: + __print_stage(); + case 5: + __print_stage(); + case 3: + __print_stage(); + case 1: + p[-1] = n + '0'; + } + } else { + switch (k) { + case 10: + __print_stage(); + case 8: + __print_stage(); + case 6: + __print_stage(); + case 4: + __print_stage(); + case 2: + __print_stage(); + } + } + return k; +} + +static int print_uint64(uint64_t n, char *p) +{ + int k = 0; + const char *dp; + const uint64_t x = 1000000000ULL; + + if (n < x) { + return print_uint32((uint32_t)n, p); + } + if(n >= 10000ULL * x) { + if(n >= 10000000ULL * x) { + if(n >= 1000000000ULL * x) { + if (n >= 10000000000ULL * x) { + k = 11 + 9; + } else { + k = 10 + 9; + } + } else if(n >= 100000000ULL * x) { + k = 9 + 9; + } else { + k = 8 + 9; + } + } else { + if(n >= 1000000ULL * x) { + k = 7 + 9; + } else if(n >= 100000ULL * x) { + k = 6 + 9; + } else { + k = 5 + 9; + } + } + } else { + if(n >= 100ULL * x) { + if(n >= 1000ULL * x) { + k = 4 + 9; + } else { + k = 3 + 9; + } + } else { + if(n >= 10ULL * x) { + k = 2 + 9; + } else { + k = 1 + 9; + } + } + } + p += k; + *p = '\0'; + if (k & 1) { + switch (k) { + case 19: + __print_stage(); + case 17: + __print_stage(); + case 15: + __print_stage(); + case 13: + __print_stage(); + case 11: + __print_stage() + __print_short_stage(); + } + } else { + switch (k) { + case 20: + __print_stage(); + case 18: + __print_stage(); + case 16: + __print_stage(); + case 14: + __print_stage(); + case 12: + __print_stage(); + case 10: + __print_stage(); + } + } + __print_long_stage() + __print_long_stage() + return k; +} + +static int print_int8(int8_t n, char *p) +{ + int sign; + + if ((sign = n < 0)) { + *p++ = '-'; + n = -n; + } + return print_uint8(n, p) + sign; +} + +static int print_int16(int16_t n, char *p) +{ + int sign; + + if ((sign = n < 0)) { + *p++ = '-'; + n = -n; + } + return print_uint16(n, p) + sign; +} + +static int print_int32(int32_t n, char *p) +{ + int sign; + + if ((sign = n < 0)) { + *p++ = '-'; + n = -n; + } + return print_uint32(n, p) + sign; +} + +static int print_int64(int64_t n, char *p) +{ + int sign; + + if ((sign = n < 0)) { + *p++ = '-'; + n = -n; + } + return print_uint64(n, p) + sign; +} + +#define __define_print_int_simple(NAME, UNAME, T, UT) \ +static int UNAME(UT n, char *buf) \ +{ \ + char tmp[20]; \ + char* p = tmp + 20; \ + char* q = p; \ + unsigned int k, m; \ + \ + while (n >= 100) { \ + p -= 2; \ + m = (unsigned int)(n % 100) * 2; \ + n /= 100; \ + __print_unaligned_copy_16(p, __print_digit_pairs + m); \ + } \ + p -= 2; \ + m = (unsigned int)n * 2; \ + __print_unaligned_copy_16(p, __print_digit_pairs + m); \ + if (n < 10) { \ + ++p; \ + } \ + k = (unsigned int)(q - p); \ + while (p != q) { \ + *buf++ = *p++; \ + } \ + *buf = '\0'; \ + return k; \ +} \ + \ +static int NAME(T n, char *buf) \ +{ \ + int sign = n < 0; \ + \ + if (sign) { \ + *buf++ = '-'; \ + n = -n; \ + } \ + return UNAME((UT)n, buf) + sign; \ +} + +__define_print_int_simple(print_int, print_uint, int, unsigned int) +__define_print_int_simple(print_long, print_ulong, long, unsigned long) + +#ifdef PPRINTINT_BENCH +int main() { + int64_t count = 10000000; /* 10^7 */ +#if 0 + int64_t base = 0; + int64_t base = 10000000000000000; /* 10^16 */ + int64_t base = 1000000000; /* 10^9 */ +#endif + int64_t base = INT64_MIN - count/2; + char buf[100]; + int i, k = 0, n = 0; + for (i = 0; i < count; i++) { + k = print_int64(i + base, buf); + n += buf[0] + buf[k - 1]; + } + return n; +} +/* Call with time on executable, multiply time in seconds by 100 to get time unit in ns/number. */ +#endif /* PPRINTINT_BENCH */ + +#ifdef PPRINTINT_TEST + +#include +#include + +int main() +{ + char buf[21]; + int failed = 0; + int k; + + k = print_uint64(UINT64_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("18446744073709551615", buf)) { + printf("UINT64_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int64(INT64_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("9223372036854775807", buf)) { + printf("INT64_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int64(INT64_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-9223372036854775808", buf)) { + printf("INT64_MIN didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_uint32(UINT32_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("4294967295", buf)) { + printf("UINT32_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int32(INT32_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("2147483647", buf)) { + printf("INT32_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int32(INT32_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-2147483648", buf)) { + printf("INT32_MIN didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_uint16(UINT16_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("65535", buf)) { + printf("UINT16_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int16(INT16_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("32767", buf)) { + printf("INT16_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int16(INT16_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-32768", buf)) { + printf("INT16_MIN didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_uint8(UINT8_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("255", buf)) { + printf("INT8_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int8(INT8_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("127", buf)) { + printf("INT8_MAX didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int8(INT8_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-128", buf)) { + printf("INT8_MIN didn't print correctly, got:\n'%s'\n", buf); + ++failed; + } + k = print_int(INT32_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("2147483647", buf)) { + printf("INT32_MAX didn't print correctly with k = print_int, got:\n'%s'\n", buf); + ++failed; + } + k = print_int(INT32_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-2147483648", buf)) { + printf("INT32_MIN didn't print correctly k = print_int, got:\n'%s'\n", buf); + ++failed; + } + k = print_long(INT32_MAX, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("2147483647", buf)) { + printf("INT32_MAX didn't print correctly with fmt_long, got:\n'%s'\n", buf); + ++failed; + } + k = print_long(INT32_MIN, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("-2147483648", buf)) { + printf("INT32_MIN didn't print correctly fmt_long, got:\n'%s'\n", buf); + ++failed; + } + k = print_bool(1, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("true", buf) { + printf("1 didn't print 'true' as expected, got:\n'%s'\n", buf); + ++failed; + } + k = print_bool(-1, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("true", buf) { + printf("-1 didn't print 'true' as expected, got:\n'%s'\n", buf); + ++failed; + } + k = print_bool(, buf); + if (strlen(buf) != k) printf("length error\n"); + if (strcmp("false", buf) { + printf("0 didn't print 'false' as expected, got:\n'%s'\n", buf); + ++failed; + } + if (failed) { + printf("FAILED\n"); + return -1; + } + printf("SUCCESS\n"); + return 0; +} +#endif /* PPRINTINT_TEST */ + +#include "pdiagnostic_pop.h" + +#endif /* PPRINTINT_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pstatic_assert.h b/Source/library/FBSUtil/flatcc/portable/pstatic_assert.h new file mode 100644 index 0000000..78bc86c --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pstatic_assert.h @@ -0,0 +1,35 @@ +#ifndef PSTATIC_ASSERT_H +#define PSTATIC_ASSERT_H + +#include + +#ifndef static_assert + +#ifndef __has_feature + #define __has_feature(x) 0 +#endif + +#if __has_feature(c_static_assert) + +#define static_assert(pred, msg) _Static_assert(pred, msg) + +#else + +#define __PSTATIC_ASSERT_CONCAT_(a, b) static_assert_scope_##a##_line_##b +#define __PSTATIC_ASSERT_CONCAT(a, b) __PSTATIC_ASSERT_CONCAT_(a, b) +#ifdef __COUNTER__ +#define static_assert(e, msg) enum { __PSTATIC_ASSERT_CONCAT(__COUNTER__, __LINE__) = 1/(!!(e)) } +#else +#include "pstatic_assert_scope.h" +#define static_assert(e, msg) enum { __PSTATIC_ASSERT_CONCAT(__PSTATIC_ASSERT_COUNTER, __LINE__) = 1/(!!(e)) } +#endif + +#endif /* __has_feature */ +#endif /* static_assert */ + +#endif /* PSTATIC_ASSERT_H */ + +/* Update scope counter outside of include guard. */ +#ifdef __PSTATIC_ASSERT_COUNTER +#include "pstatic_assert_scope.h" +#endif diff --git a/Source/library/FBSUtil/flatcc/portable/pstatic_assert_scope.h b/Source/library/FBSUtil/flatcc/portable/pstatic_assert_scope.h new file mode 100644 index 0000000..71a0c29 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pstatic_assert_scope.h @@ -0,0 +1,280 @@ +/* + * january, 2017, ported to portable library by mikkelfj. + * Based on dbgtools static assert counter, but with renamed macros. + */ + +/* + dbgtools - platform independent wrapping of "nice to have" debug functions. + + version 0.1, october, 2013 + + https://github.com/wc-duck/dbgtools + + Copyright (C) 2013- Fredrik Kihlander + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Fredrik Kihlander +*/ + +/** + * Auto-generated header implementing a counter that increases by each include of the file. + * + * This header will define the macro __PSTATIC_ASSERT_COUNTER to be increased for each inclusion of the file. + * + * It has been generated with 3 amount of digits resulting in the counter wrapping around after + * 10000 inclusions. + * + * Usage: + * + * #include "this_header.h" + * int a = __PSTATIC_ASSERT_COUNTER; // 0 + * #include "this_header.h" + * int b = __PSTATIC_ASSERT_COUNTER; // 1 + * #include "this_header.h" + * int c = __PSTATIC_ASSERT_COUNTER; // 2 + * #include "this_header.h" + * int d = __PSTATIC_ASSERT_COUNTER; // 3 + */ + +#ifndef __PSTATIC_ASSERT_COUNTER +# define __PSTATIC_ASSERT_COUNTER_0 0 +# define __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_D1_0 +# define __PSTATIC_ASSERT_COUNTER_D2_0 +# define __PSTATIC_ASSERT_COUNTER_D3_0 +#endif /* __PSTATIC_ASSERT_COUNTER */ + +#if !defined( __PSTATIC_ASSERT_COUNTER_D0_0 ) +# define __PSTATIC_ASSERT_COUNTER_D0_0 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 0 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_1 ) +# define __PSTATIC_ASSERT_COUNTER_D0_1 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 1 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_2 ) +# define __PSTATIC_ASSERT_COUNTER_D0_2 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 2 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_3 ) +# define __PSTATIC_ASSERT_COUNTER_D0_3 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 3 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_4 ) +# define __PSTATIC_ASSERT_COUNTER_D0_4 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 4 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_5 ) +# define __PSTATIC_ASSERT_COUNTER_D0_5 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 5 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_6 ) +# define __PSTATIC_ASSERT_COUNTER_D0_6 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 6 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_7 ) +# define __PSTATIC_ASSERT_COUNTER_D0_7 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 7 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_8 ) +# define __PSTATIC_ASSERT_COUNTER_D0_8 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 8 +#elif !defined( __PSTATIC_ASSERT_COUNTER_D0_9 ) +# define __PSTATIC_ASSERT_COUNTER_D0_9 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 9 +#else +# undef __PSTATIC_ASSERT_COUNTER_D0_1 +# undef __PSTATIC_ASSERT_COUNTER_D0_2 +# undef __PSTATIC_ASSERT_COUNTER_D0_3 +# undef __PSTATIC_ASSERT_COUNTER_D0_4 +# undef __PSTATIC_ASSERT_COUNTER_D0_5 +# undef __PSTATIC_ASSERT_COUNTER_D0_6 +# undef __PSTATIC_ASSERT_COUNTER_D0_7 +# undef __PSTATIC_ASSERT_COUNTER_D0_8 +# undef __PSTATIC_ASSERT_COUNTER_D0_9 +# undef __PSTATIC_ASSERT_COUNTER_0 +# define __PSTATIC_ASSERT_COUNTER_0 0 +# if !defined( __PSTATIC_ASSERT_COUNTER_D1_0 ) +# define __PSTATIC_ASSERT_COUNTER_D1_0 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 0 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_1 ) +# define __PSTATIC_ASSERT_COUNTER_D1_1 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 1 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_2 ) +# define __PSTATIC_ASSERT_COUNTER_D1_2 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 2 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_3 ) +# define __PSTATIC_ASSERT_COUNTER_D1_3 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 3 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_4 ) +# define __PSTATIC_ASSERT_COUNTER_D1_4 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 4 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_5 ) +# define __PSTATIC_ASSERT_COUNTER_D1_5 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 5 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_6 ) +# define __PSTATIC_ASSERT_COUNTER_D1_6 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 6 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_7 ) +# define __PSTATIC_ASSERT_COUNTER_D1_7 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 7 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_8 ) +# define __PSTATIC_ASSERT_COUNTER_D1_8 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 8 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D1_9 ) +# define __PSTATIC_ASSERT_COUNTER_D1_9 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 9 +# else +# undef __PSTATIC_ASSERT_COUNTER_D1_1 +# undef __PSTATIC_ASSERT_COUNTER_D1_2 +# undef __PSTATIC_ASSERT_COUNTER_D1_3 +# undef __PSTATIC_ASSERT_COUNTER_D1_4 +# undef __PSTATIC_ASSERT_COUNTER_D1_5 +# undef __PSTATIC_ASSERT_COUNTER_D1_6 +# undef __PSTATIC_ASSERT_COUNTER_D1_7 +# undef __PSTATIC_ASSERT_COUNTER_D1_8 +# undef __PSTATIC_ASSERT_COUNTER_D1_9 +# undef __PSTATIC_ASSERT_COUNTER_1 +# define __PSTATIC_ASSERT_COUNTER_1 0 +# if !defined( __PSTATIC_ASSERT_COUNTER_D2_0 ) +# define __PSTATIC_ASSERT_COUNTER_D2_0 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 0 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_1 ) +# define __PSTATIC_ASSERT_COUNTER_D2_1 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 1 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_2 ) +# define __PSTATIC_ASSERT_COUNTER_D2_2 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 2 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_3 ) +# define __PSTATIC_ASSERT_COUNTER_D2_3 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 3 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_4 ) +# define __PSTATIC_ASSERT_COUNTER_D2_4 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 4 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_5 ) +# define __PSTATIC_ASSERT_COUNTER_D2_5 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 5 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_6 ) +# define __PSTATIC_ASSERT_COUNTER_D2_6 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 6 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_7 ) +# define __PSTATIC_ASSERT_COUNTER_D2_7 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 7 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_8 ) +# define __PSTATIC_ASSERT_COUNTER_D2_8 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 8 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D2_9 ) +# define __PSTATIC_ASSERT_COUNTER_D2_9 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 9 +# else +# undef __PSTATIC_ASSERT_COUNTER_D2_1 +# undef __PSTATIC_ASSERT_COUNTER_D2_2 +# undef __PSTATIC_ASSERT_COUNTER_D2_3 +# undef __PSTATIC_ASSERT_COUNTER_D2_4 +# undef __PSTATIC_ASSERT_COUNTER_D2_5 +# undef __PSTATIC_ASSERT_COUNTER_D2_6 +# undef __PSTATIC_ASSERT_COUNTER_D2_7 +# undef __PSTATIC_ASSERT_COUNTER_D2_8 +# undef __PSTATIC_ASSERT_COUNTER_D2_9 +# undef __PSTATIC_ASSERT_COUNTER_2 +# define __PSTATIC_ASSERT_COUNTER_2 0 +# if !defined( __PSTATIC_ASSERT_COUNTER_D3_0 ) +# define __PSTATIC_ASSERT_COUNTER_D3_0 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 0 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_1 ) +# define __PSTATIC_ASSERT_COUNTER_D3_1 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 1 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_2 ) +# define __PSTATIC_ASSERT_COUNTER_D3_2 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 2 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_3 ) +# define __PSTATIC_ASSERT_COUNTER_D3_3 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 3 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_4 ) +# define __PSTATIC_ASSERT_COUNTER_D3_4 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 4 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_5 ) +# define __PSTATIC_ASSERT_COUNTER_D3_5 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 5 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_6 ) +# define __PSTATIC_ASSERT_COUNTER_D3_6 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 6 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_7 ) +# define __PSTATIC_ASSERT_COUNTER_D3_7 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 7 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_8 ) +# define __PSTATIC_ASSERT_COUNTER_D3_8 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 8 +# elif !defined( __PSTATIC_ASSERT_COUNTER_D3_9 ) +# define __PSTATIC_ASSERT_COUNTER_D3_9 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 9 +# else +# undef __PSTATIC_ASSERT_COUNTER_D3_1 +# undef __PSTATIC_ASSERT_COUNTER_D3_2 +# undef __PSTATIC_ASSERT_COUNTER_D3_3 +# undef __PSTATIC_ASSERT_COUNTER_D3_4 +# undef __PSTATIC_ASSERT_COUNTER_D3_5 +# undef __PSTATIC_ASSERT_COUNTER_D3_6 +# undef __PSTATIC_ASSERT_COUNTER_D3_7 +# undef __PSTATIC_ASSERT_COUNTER_D3_8 +# undef __PSTATIC_ASSERT_COUNTER_D3_9 +# undef __PSTATIC_ASSERT_COUNTER_3 +# define __PSTATIC_ASSERT_COUNTER_3 0 +# endif +# endif +# endif +#endif + +#define __PSTATIC_ASSERT_COUNTER_JOIN_DIGITS_MACRO_(digit0,digit1,digit2,digit3) digit0##digit1##digit2##digit3 +#define __PSTATIC_ASSERT_COUNTER_JOIN_DIGITS_MACRO(digit0,digit1,digit2,digit3) __PSTATIC_ASSERT_COUNTER_JOIN_DIGITS_MACRO_(digit0,digit1,digit2,digit3) +#undef __PSTATIC_ASSERT_COUNTER +#define __PSTATIC_ASSERT_COUNTER __PSTATIC_ASSERT_COUNTER_JOIN_DIGITS_MACRO(__PSTATIC_ASSERT_COUNTER_3,__PSTATIC_ASSERT_COUNTER_2,__PSTATIC_ASSERT_COUNTER_1,__PSTATIC_ASSERT_COUNTER_0) diff --git a/Source/library/FBSUtil/flatcc/portable/pstdalign.h b/Source/library/FBSUtil/flatcc/portable/pstdalign.h new file mode 100644 index 0000000..9dec968 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pstdalign.h @@ -0,0 +1,63 @@ +#ifndef PSTDALIGN_H +#define PSTDALIGN_H + +/* + * NOTE: aligned_alloc is defined via paligned_alloc.h + * and requires aligned_free to be fully portable although + * free also works on C11 and platforms with posix_memalign. + * + * NOTE: C++11 defines alignas as a keyword but then also defines + * __alignas_is_defined. + */ + +#ifndef __alignas_is_defined + +#ifdef __cplusplus +extern "C" { +#endif + +#if (!defined(__clang__) && defined(__GNUC__) && \ + ((__GNUC__ < 4) || (__GNUC__ == 4 && __GNUC_MINOR__ < 7))) +#undef PORTABLE_C11_STDALIGN_MISSING +#define PORTABLE_C11_STDALIGN_MISSING +#endif + +#if defined(__IBMC__) +#undef PORTABLE_C11_STDALIGN_MISSING +#define PORTABLE_C11_STDALIGN_MISSING +#endif + +#if ((defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) && \ + !defined(PORTABLE_C11_STDALIGN_MISSING)) +/* C11 or newer */ +#include +#else +#if defined(__GNUC__) || defined(__IBM_ALIGNOF__) || defined(__clang__) +#define _Alignas(t) __attribute__((__aligned__(t))) +#define _Alignof(t) __alignof__(t) +#elif defined(_MSC_VER) +#define _Alignas(t) __declspec (align(t)) +#define _Alignof(t) __alignof(t) + +#else +#error please update pstdalign.h with support for current compiler +#endif + +#endif /* __STDC__ */ + +#define alignas _Alignas +#define alignof _Alignof + +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +#ifdef __cplusplus +} +#endif + +#endif /* __alignas__is_defined */ + +#include "paligned_alloc.h" + + +#endif /* PSTDALIGN_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pstdbool.h b/Source/library/FBSUtil/flatcc/portable/pstdbool.h new file mode 100644 index 0000000..28fc89c --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pstdbool.h @@ -0,0 +1,37 @@ +#ifndef PSTDBOOL_H +#define PSTDBOOL_H + +#if !defined(__cplusplus) && !__bool_true_false_are_defined && !defined(bool) && !defined(__STDBOOL_H) + +#ifdef HAVE_STDBOOL_H + +#include + +#elif (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +/* C99 or newer */ + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#elif defined(__GNUC__) && !defined(__STRICT_ANSI__) + +#define bool bool +#define true true +#define false false +#define __bool_true_false_are_defined 1 + +#else + +typedef unsigned char _Portable_bool; +#define bool _Portable_bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif + +#endif + +#endif /* PSTDBOOL_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pstdint.h b/Source/library/FBSUtil/flatcc/portable/pstdint.h new file mode 100644 index 0000000..14444aa --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pstdint.h @@ -0,0 +1,898 @@ +/* A portable stdint.h + **************************************************************************** + * BSD License: + **************************************************************************** + * + * Copyright (c) 2005-2016 Paul Hsieh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + **************************************************************************** + * + * Version 0.1.15.2 + * + * The ANSI C standard committee, for the C99 standard, specified the + * inclusion of a new standard include file called stdint.h. This is + * a very useful and long desired include file which contains several + * very precise definitions for integer scalar types that is + * critically important for making portable several classes of + * applications including cryptography, hashing, variable length + * integer libraries and so on. But for most developers its likely + * useful just for programming sanity. + * + * The problem is that some compiler vendors chose to ignore the C99 + * standard and some older compilers have no opportunity to be updated. + * Because of this situation, simply including stdint.h in your code + * makes it unportable. + * + * So that's what this file is all about. Its an attempt to build a + * single universal include file that works on as many platforms as + * possible to deliver what stdint.h is supposed to. Even compilers + * that already come with stdint.h can use this file instead without + * any loss of functionality. A few things that should be noted about + * this file: + * + * 1) It is not guaranteed to be portable and/or present an identical + * interface on all platforms. The extreme variability of the + * ANSI C standard makes this an impossibility right from the + * very get go. Its really only meant to be useful for the vast + * majority of platforms that possess the capability of + * implementing usefully and precisely defined, standard sized + * integer scalars. Systems which are not intrinsically 2s + * complement may produce invalid constants. + * + * 2) There is an unavoidable use of non-reserved symbols. + * + * 3) Other standard include files are invoked. + * + * 4) This file may come in conflict with future platforms that do + * include stdint.h. The hope is that one or the other can be + * used with no real difference. + * + * 5) In the current verison, if your platform can't represent + * int32_t, int16_t and int8_t, it just dumps out with a compiler + * error. + * + * 6) 64 bit integers may or may not be defined. Test for their + * presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX. + * Note that this is different from the C99 specification which + * requires the existence of 64 bit support in the compiler. If + * this is not defined for your platform, yet it is capable of + * dealing with 64 bits then it is because this file has not yet + * been extended to cover all of your system's capabilities. + * + * 7) (u)intptr_t may or may not be defined. Test for its presence + * with the test: #ifdef PTRDIFF_MAX. If this is not defined + * for your platform, then it is because this file has not yet + * been extended to cover all of your system's capabilities, not + * because its optional. + * + * 8) The following might not been defined even if your platform is + * capable of defining it: + * + * WCHAR_MIN + * WCHAR_MAX + * (u)int64_t + * PTRDIFF_MIN + * PTRDIFF_MAX + * (u)intptr_t + * + * 9) The following have not been defined: + * + * WINT_MIN + * WINT_MAX + * + * 10) The criteria for defining (u)int_least(*)_t isn't clear, + * except for systems which don't have a type that precisely + * defined 8, 16, or 32 bit types (which this include file does + * not support anyways). Default definitions have been given. + * + * 11) The criteria for defining (u)int_fast(*)_t isn't something I + * would trust to any particular compiler vendor or the ANSI C + * committee. It is well known that "compatible systems" are + * commonly created that have very different performance + * characteristics from the systems they are compatible with, + * especially those whose vendors make both the compiler and the + * system. Default definitions have been given, but its strongly + * recommended that users never use these definitions for any + * reason (they do *NOT* deliver any serious guarantee of + * improved performance -- not in this file, nor any vendor's + * stdint.h). + * + * 12) The following macros: + * + * PRINTF_INTMAX_MODIFIER + * PRINTF_INT64_MODIFIER + * PRINTF_INT32_MODIFIER + * PRINTF_INT16_MODIFIER + * PRINTF_LEAST64_MODIFIER + * PRINTF_LEAST32_MODIFIER + * PRINTF_LEAST16_MODIFIER + * PRINTF_INTPTR_MODIFIER + * + * are strings which have been defined as the modifiers required + * for the "d", "u" and "x" printf formats to correctly output + * (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t, + * (u)least32_t, (u)least16_t and (u)intptr_t types respectively. + * PRINTF_INTPTR_MODIFIER is not defined for some systems which + * provide their own stdint.h. PRINTF_INT64_MODIFIER is not + * defined if INT64_MAX is not defined. These are an extension + * beyond what C99 specifies must be in stdint.h. + * + * In addition, the following macros are defined: + * + * PRINTF_INTMAX_HEX_WIDTH + * PRINTF_INT64_HEX_WIDTH + * PRINTF_INT32_HEX_WIDTH + * PRINTF_INT16_HEX_WIDTH + * PRINTF_INT8_HEX_WIDTH + * PRINTF_INTMAX_DEC_WIDTH + * PRINTF_INT64_DEC_WIDTH + * PRINTF_INT32_DEC_WIDTH + * PRINTF_INT16_DEC_WIDTH + * PRINTF_UINT8_DEC_WIDTH + * PRINTF_UINTMAX_DEC_WIDTH + * PRINTF_UINT64_DEC_WIDTH + * PRINTF_UINT32_DEC_WIDTH + * PRINTF_UINT16_DEC_WIDTH + * PRINTF_UINT8_DEC_WIDTH + * + * Which specifies the maximum number of characters required to + * print the number of that type in either hexadecimal or decimal. + * These are an extension beyond what C99 specifies must be in + * stdint.h. + * + * Compilers tested (all with 0 warnings at their highest respective + * settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32 + * bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio + * .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3 + * + * This file should be considered a work in progress. Suggestions for + * improvements, especially those which increase coverage are strongly + * encouraged. + * + * Acknowledgements + * + * The following people have made significant contributions to the + * development and testing of this file: + * + * Chris Howie + * John Steele Scott + * Dave Thorup + * John Dill + * Florian Wobbe + * Christopher Sean Morrison + * Mikkel Fahnoe Jorgensen + * + */ + +#include +#include +#include + +/* + * For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and + * do nothing else. On the Mac OS X version of gcc this is _STDINT_H_. + */ + +#if ((defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (__GNUC__ > 3 || defined(_STDINT_H) || defined(_STDINT_H_) || defined (__UINT_FAST64_TYPE__)) )) && !defined (_PSTDINT_H_INCLUDED) +#include +#define _PSTDINT_H_INCLUDED +# if defined(__GNUC__) && (defined(__x86_64__) || defined(__ppc64__)) && !(defined(__APPLE__) && defined(__MACH__)) +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "l" +# endif +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +# else +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# ifndef PRINTF_INT32_MODIFIER +# if (UINT_MAX == UINT32_MAX) +# define PRINTF_INT32_MODIFIER "" +# else +# define PRINTF_INT32_MODIFIER "l" +# endif +# endif +# endif +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER +# endif +# ifndef PRINTF_INT64_HEX_WIDTH +# define PRINTF_INT64_HEX_WIDTH "16" +# endif +# ifndef PRINTF_UINT64_HEX_WIDTH +# define PRINTF_UINT64_HEX_WIDTH "16" +# endif +# ifndef PRINTF_INT32_HEX_WIDTH +# define PRINTF_INT32_HEX_WIDTH "8" +# endif +# ifndef PRINTF_UINT32_HEX_WIDTH +# define PRINTF_UINT32_HEX_WIDTH "8" +# endif +# ifndef PRINTF_INT16_HEX_WIDTH +# define PRINTF_INT16_HEX_WIDTH "4" +# endif +# ifndef PRINTF_UINT16_HEX_WIDTH +# define PRINTF_UINT16_HEX_WIDTH "4" +# endif +# ifndef PRINTF_INT8_HEX_WIDTH +# define PRINTF_INT8_HEX_WIDTH "2" +# endif +# ifndef PRINTF_UINT8_HEX_WIDTH +# define PRINTF_UINT8_HEX_WIDTH "2" +# endif +# ifndef PRINTF_INT64_DEC_WIDTH +# define PRINTF_INT64_DEC_WIDTH "19" +# endif +# ifndef PRINTF_UINT64_DEC_WIDTH +# define PRINTF_UINT64_DEC_WIDTH "20" +# endif +# ifndef PRINTF_INT32_DEC_WIDTH +# define PRINTF_INT32_DEC_WIDTH "10" +# endif +# ifndef PRINTF_UINT32_DEC_WIDTH +# define PRINTF_UINT32_DEC_WIDTH "10" +# endif +# ifndef PRINTF_INT16_DEC_WIDTH +# define PRINTF_INT16_DEC_WIDTH "5" +# endif +# ifndef PRINTF_UINT16_DEC_WIDTH +# define PRINTF_UINT16_DEC_WIDTH "5" +# endif +# ifndef PRINTF_INT8_DEC_WIDTH +# define PRINTF_INT8_DEC_WIDTH "3" +# endif +# ifndef PRINTF_UINT8_DEC_WIDTH +# define PRINTF_UINT8_DEC_WIDTH "3" +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH +# endif +# ifndef PRINTF_UINTMAX_HEX_WIDTH +# define PRINTF_UINTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH +# endif +# ifndef PRINTF_UINTMAX_DEC_WIDTH +# define PRINTF_UINTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH +# endif + +/* + * Something really weird is going on with Open Watcom. Just pull some of + * these duplicated definitions from Open Watcom's stdint.h file for now. + */ + +# if defined (__WATCOMC__) && __WATCOMC__ >= 1250 +# if !defined (INT64_C) +# define INT64_C(x) (x + (INT64_MAX - INT64_MAX)) +# endif +# if !defined (UINT64_C) +# define UINT64_C(x) (x + (UINT64_MAX - UINT64_MAX)) +# endif +# if !defined (INT32_C) +# define INT32_C(x) (x + (INT32_MAX - INT32_MAX)) +# endif +# if !defined (UINT32_C) +# define UINT32_C(x) (x + (UINT32_MAX - UINT32_MAX)) +# endif +# if !defined (INT16_C) +# define INT16_C(x) (x) +# endif +# if !defined (UINT16_C) +# define UINT16_C(x) (x) +# endif +# if !defined (INT8_C) +# define INT8_C(x) (x) +# endif +# if !defined (UINT8_C) +# define UINT8_C(x) (x) +# endif +# if !defined (UINT64_MAX) +# define UINT64_MAX 18446744073709551615ULL +# endif +# if !defined (INT64_MAX) +# define INT64_MAX 9223372036854775807LL +# endif +# if !defined (UINT32_MAX) +# define UINT32_MAX 4294967295UL +# endif +# if !defined (INT32_MAX) +# define INT32_MAX 2147483647L +# endif +# if !defined (INTMAX_MAX) +# define INTMAX_MAX INT64_MAX +# endif +# if !defined (INTMAX_MIN) +# define INTMAX_MIN INT64_MIN +# endif +# endif +#endif + +#ifndef _PSTDINT_H_INCLUDED +#define _PSTDINT_H_INCLUDED + +#ifndef SIZE_MAX +# define SIZE_MAX (~(size_t)0) +#endif + +/* + * Deduce the type assignments from limits.h under the assumption that + * integer sizes in bits are powers of 2, and follow the ANSI + * definitions. + */ + +#ifndef UINT8_MAX +# define UINT8_MAX 0xff +#endif +#if !defined(uint8_t) && !defined(_UINT8_T) +# if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S) + typedef unsigned char uint8_t; +# define UINT8_C(v) ((uint8_t) v) +# else +# error "Platform not supported" +# endif +#endif + +#ifndef INT8_MAX +# define INT8_MAX 0x7f +#endif +#ifndef INT8_MIN +# define INT8_MIN INT8_C(0x80) +#endif +#if !defined(int8_t) && !defined(_INT8_T) +# if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S) + typedef signed char int8_t; +# define INT8_C(v) ((int8_t) v) +# else +# error "Platform not supported" +# endif +#endif + +#ifndef UINT16_MAX +# define UINT16_MAX 0xffff +#endif +#if !defined(uint16_t) && !defined(_UINT16_T) +#if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S) + typedef unsigned int uint16_t; +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "" +# endif +# define UINT16_C(v) ((uint16_t) (v)) +#elif (USHRT_MAX == UINT16_MAX) + typedef unsigned short uint16_t; +# define UINT16_C(v) ((uint16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef INT16_MAX +# define INT16_MAX 0x7fff +#endif +#ifndef INT16_MIN +# define INT16_MIN INT16_C(0x8000) +#endif +#if !defined(int16_t) && !defined(_INT16_T) +#if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S) + typedef signed int int16_t; +# define INT16_C(v) ((int16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "" +# endif +#elif (SHRT_MAX == INT16_MAX) + typedef signed short int16_t; +# define INT16_C(v) ((int16_t) (v)) +# ifndef PRINTF_INT16_MODIFIER +# define PRINTF_INT16_MODIFIER "h" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef UINT32_MAX +# define UINT32_MAX (0xffffffffUL) +#endif +#if !defined(uint32_t) && !defined(_UINT32_T) +#if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S) + typedef unsigned long uint32_t; +# define UINT32_C(v) v ## UL +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "l" +# endif +#elif (UINT_MAX == UINT32_MAX) + typedef unsigned int uint32_t; +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +# define UINT32_C(v) v ## U +#elif (USHRT_MAX == UINT32_MAX) + typedef unsigned short uint32_t; +# define UINT32_C(v) ((unsigned short) (v)) +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#else +#error "Platform not supported" +#endif +#endif + +#ifndef INT32_MAX +# define INT32_MAX (0x7fffffffL) +#endif +#ifndef INT32_MIN +# define INT32_MIN INT32_C(0x80000000) +#endif +#if !defined(int32_t) && !defined(_INT32_T) +#if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S) + typedef signed long int32_t; +# define INT32_C(v) v ## L +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "l" +# endif +#elif (INT_MAX == INT32_MAX) + typedef signed int int32_t; +# define INT32_C(v) v +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#elif (SHRT_MAX == INT32_MAX) + typedef signed short int32_t; +# define INT32_C(v) ((short) (v)) +# ifndef PRINTF_INT32_MODIFIER +# define PRINTF_INT32_MODIFIER "" +# endif +#else +#error "Platform not supported" +#endif +#endif + +/* + * The macro stdint_int64_defined is temporarily used to record + * whether or not 64 integer support is available. It must be + * defined for any 64 integer extensions for new platforms that are + * added. + */ + +#undef stdint_int64_defined +#if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S) +# if (__STDC__ && __STDC_VERSION__ >= 199901L) || defined (S_SPLINT_S) +# define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# endif +#endif + +#if !defined (stdint_int64_defined) +# if defined(__GNUC__) +# define stdint_int64_defined + __extension__ typedef long long int64_t; + __extension__ typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S) +# define stdint_int64_defined + typedef long long int64_t; + typedef unsigned long long uint64_t; +# define UINT64_C(v) v ## ULL +# define INT64_C(v) v ## LL +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "ll" +# endif +# elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC) +# define stdint_int64_defined + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; +# define UINT64_C(v) v ## UI64 +# define INT64_C(v) v ## I64 +# ifndef PRINTF_INT64_MODIFIER +# define PRINTF_INT64_MODIFIER "I64" +# endif +# endif +#endif + +#if !defined (LONG_LONG_MAX) && defined (INT64_C) +# define LONG_LONG_MAX INT64_C (9223372036854775807) +#endif +#ifndef ULONG_LONG_MAX +# define ULONG_LONG_MAX UINT64_C (18446744073709551615) +#endif + +#if !defined (INT64_MAX) && defined (INT64_C) +# define INT64_MAX INT64_C (9223372036854775807) +#endif +#if !defined (INT64_MIN) && defined (INT64_C) +# define INT64_MIN INT64_C (-9223372036854775808) +#endif +#if !defined (UINT64_MAX) && defined (INT64_C) +# define UINT64_MAX UINT64_C (18446744073709551615) +#endif + +/* + * Width of hexadecimal for number field. + */ + +#ifndef PRINTF_INT64_HEX_WIDTH +# define PRINTF_INT64_HEX_WIDTH "16" +#endif +#ifndef PRINTF_INT32_HEX_WIDTH +# define PRINTF_INT32_HEX_WIDTH "8" +#endif +#ifndef PRINTF_INT16_HEX_WIDTH +# define PRINTF_INT16_HEX_WIDTH "4" +#endif +#ifndef PRINTF_INT8_HEX_WIDTH +# define PRINTF_INT8_HEX_WIDTH "2" +#endif +#ifndef PRINTF_INT64_DEC_WIDTH +# define PRINTF_INT64_DEC_WIDTH "19" +#endif +#ifndef PRINTF_INT32_DEC_WIDTH +# define PRINTF_INT32_DEC_WIDTH "10" +#endif +#ifndef PRINTF_INT16_DEC_WIDTH +# define PRINTF_INT16_DEC_WIDTH "5" +#endif +#ifndef PRINTF_INT8_DEC_WIDTH +# define PRINTF_INT8_DEC_WIDTH "3" +#endif +#ifndef PRINTF_UINT64_DEC_WIDTH +# define PRINTF_UINT64_DEC_WIDTH "20" +#endif +#ifndef PRINTF_UINT32_DEC_WIDTH +# define PRINTF_UINT32_DEC_WIDTH "10" +#endif +#ifndef PRINTF_UINT16_DEC_WIDTH +# define PRINTF_UINT16_DEC_WIDTH "5" +#endif +#ifndef PRINTF_UINT8_DEC_WIDTH +# define PRINTF_UINT8_DEC_WIDTH "3" +#endif + +/* + * Ok, lets not worry about 128 bit integers for now. Moore's law says + * we don't need to worry about that until about 2040 at which point + * we'll have bigger things to worry about. + */ + +#ifdef stdint_int64_defined + typedef int64_t intmax_t; + typedef uint64_t uintmax_t; +# define INTMAX_MAX INT64_MAX +# define INTMAX_MIN INT64_MIN +# define UINTMAX_MAX UINT64_MAX +# define UINTMAX_C(v) UINT64_C(v) +# define INTMAX_C(v) INT64_C(v) +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH +# endif +#else + typedef int32_t intmax_t; + typedef uint32_t uintmax_t; +# define INTMAX_MAX INT32_MAX +# define UINTMAX_MAX UINT32_MAX +# define UINTMAX_C(v) UINT32_C(v) +# define INTMAX_C(v) INT32_C(v) +# ifndef PRINTF_INTMAX_MODIFIER +# define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER +# endif +# ifndef PRINTF_INTMAX_HEX_WIDTH +# define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH +# endif +# ifndef PRINTF_INTMAX_DEC_WIDTH +# define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH +# endif +#endif + +/* + * Because this file currently only supports platforms which have + * precise powers of 2 as bit sizes for the default integers, the + * least definitions are all trivial. Its possible that a future + * version of this file could have different definitions. + */ + +#ifndef stdint_least_defined + typedef int8_t int_least8_t; + typedef uint8_t uint_least8_t; + typedef int16_t int_least16_t; + typedef uint16_t uint_least16_t; + typedef int32_t int_least32_t; + typedef uint32_t uint_least32_t; +# define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER +# define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER +# define UINT_LEAST8_MAX UINT8_MAX +# define INT_LEAST8_MAX INT8_MAX +# define UINT_LEAST16_MAX UINT16_MAX +# define INT_LEAST16_MAX INT16_MAX +# define UINT_LEAST32_MAX UINT32_MAX +# define INT_LEAST32_MAX INT32_MAX +# define INT_LEAST8_MIN INT8_MIN +# define INT_LEAST16_MIN INT16_MIN +# define INT_LEAST32_MIN INT32_MIN +# ifdef stdint_int64_defined + typedef int64_t int_least64_t; + typedef uint64_t uint_least64_t; +# define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER +# define UINT_LEAST64_MAX UINT64_MAX +# define INT_LEAST64_MAX INT64_MAX +# define INT_LEAST64_MIN INT64_MIN +# endif +#endif +#undef stdint_least_defined + +/* + * The ANSI C committee pretending to know or specify anything about + * performance is the epitome of misguided arrogance. The mandate of + * this file is to *ONLY* ever support that absolute minimum + * definition of the fast integer types, for compatibility purposes. + * No extensions, and no attempt to suggest what may or may not be a + * faster integer type will ever be made in this file. Developers are + * warned to stay away from these types when using this or any other + * stdint.h. + */ + +typedef int_least8_t int_fast8_t; +typedef uint_least8_t uint_fast8_t; +typedef int_least16_t int_fast16_t; +typedef uint_least16_t uint_fast16_t; +typedef int_least32_t int_fast32_t; +typedef uint_least32_t uint_fast32_t; +#define UINT_FAST8_MAX UINT_LEAST8_MAX +#define INT_FAST8_MAX INT_LEAST8_MAX +#define UINT_FAST16_MAX UINT_LEAST16_MAX +#define INT_FAST16_MAX INT_LEAST16_MAX +#define UINT_FAST32_MAX UINT_LEAST32_MAX +#define INT_FAST32_MAX INT_LEAST32_MAX +#define INT_FAST8_MIN INT_LEAST8_MIN +#define INT_FAST16_MIN INT_LEAST16_MIN +#define INT_FAST32_MIN INT_LEAST32_MIN +#ifdef stdint_int64_defined + typedef int_least64_t int_fast64_t; + typedef uint_least64_t uint_fast64_t; +# define UINT_FAST64_MAX UINT_LEAST64_MAX +# define INT_FAST64_MAX INT_LEAST64_MAX +# define INT_FAST64_MIN INT_LEAST64_MIN +#endif + +#undef stdint_int64_defined + +/* + * Whatever piecemeal, per compiler thing we can do about the wchar_t + * type limits. + */ + +#if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) +# include +# ifndef WCHAR_MIN +# define WCHAR_MIN 0 +# endif +# ifndef WCHAR_MAX +# define WCHAR_MAX ((wchar_t)-1) +# endif +#endif + +/* + * Whatever piecemeal, per compiler/platform thing we can do about the + * (u)intptr_t types and limits. + */ + +#if (defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED)) || defined (_UINTPTR_T) +# define STDINT_H_UINTPTR_T_DEFINED +#endif + +#ifndef STDINT_H_UINTPTR_T_DEFINED +# if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) || defined (__ppc64__) +# define stdint_intptr_bits 64 +# elif defined (__WATCOMC__) || defined (__TURBOC__) +# if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__) +# define stdint_intptr_bits 16 +# else +# define stdint_intptr_bits 32 +# endif +# elif defined (__i386__) || defined (_WIN32) || defined (WIN32) || defined (__ppc64__) +# define stdint_intptr_bits 32 +# elif defined (__INTEL_COMPILER) +/* TODO -- what did Intel do about x86-64? */ +# else +/* #error "This platform might not be supported yet" */ +# endif + +# ifdef stdint_intptr_bits +# define stdint_intptr_glue3_i(a,b,c) a##b##c +# define stdint_intptr_glue3(a,b,c) stdint_intptr_glue3_i(a,b,c) +# ifndef PRINTF_INTPTR_MODIFIER +# define PRINTF_INTPTR_MODIFIER stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER) +# endif +# ifndef PTRDIFF_MAX +# define PTRDIFF_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) +# endif +# ifndef PTRDIFF_MIN +# define PTRDIFF_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) +# endif +# ifndef UINTPTR_MAX +# define UINTPTR_MAX stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX) +# endif +# ifndef INTPTR_MAX +# define INTPTR_MAX stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX) +# endif +# ifndef INTPTR_MIN +# define INTPTR_MIN stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN) +# endif +# ifndef INTPTR_C +# define INTPTR_C(x) stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x) +# endif +# ifndef UINTPTR_C +# define UINTPTR_C(x) stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x) +# endif + typedef stdint_intptr_glue3(uint,stdint_intptr_bits,_t) uintptr_t; + typedef stdint_intptr_glue3( int,stdint_intptr_bits,_t) intptr_t; +# else +/* TODO -- This following is likely wrong for some platforms, and does + nothing for the definition of uintptr_t. */ + typedef ptrdiff_t intptr_t; +# endif +# define STDINT_H_UINTPTR_T_DEFINED +#endif + +/* + * Assumes sig_atomic_t is signed and we have a 2s complement machine. + */ + +#ifndef SIG_ATOMIC_MAX +# define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1) +#endif + +#endif + +#if defined (__TEST_PSTDINT_FOR_CORRECTNESS) + +/* + * Please compile with the maximum warning settings to make sure macros are + * not defined more than once. + */ + +#include +#include +#include + +#define glue3_aux(x,y,z) x ## y ## z +#define glue3(x,y,z) glue3_aux(x,y,z) + +#define DECLU(bits) glue3(uint,bits,_t) glue3(u,bits,) = glue3(UINT,bits,_C) (0); +#define DECLI(bits) glue3(int,bits,_t) glue3(i,bits,) = glue3(INT,bits,_C) (0); + +#define DECL(us,bits) glue3(DECL,us,) (bits) + +#define TESTUMAX(bits) glue3(u,bits,) = ~glue3(u,bits,); if (glue3(UINT,bits,_MAX) != glue3(u,bits,)) printf ("Something wrong with UINT%d_MAX\n", bits) + +#define REPORTERROR(msg) { err_n++; if (err_first <= 0) err_first = __LINE__; printf msg; } + +int main () { + int err_n = 0; + int err_first = 0; + DECL(I,8) + DECL(U,8) + DECL(I,16) + DECL(U,16) + DECL(I,32) + DECL(U,32) +#ifdef INT64_MAX + DECL(I,64) + DECL(U,64) +#endif + intmax_t imax = INTMAX_C(0); + uintmax_t umax = UINTMAX_C(0); + char str0[256], str1[256]; + + sprintf (str0, "%" PRINTF_INT32_MODIFIER "d", INT32_C(2147483647)); + if (0 != strcmp (str0, "2147483647")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str0)); + if (atoi(PRINTF_INT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR (("Something wrong with PRINTF_INT32_DEC_WIDTH : %s\n", PRINTF_INT32_DEC_WIDTH)); + sprintf (str0, "%" PRINTF_INT32_MODIFIER "u", UINT32_C(4294967295)); + if (0 != strcmp (str0, "4294967295")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str0)); + if (atoi(PRINTF_UINT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR (("Something wrong with PRINTF_UINT32_DEC_WIDTH : %s\n", PRINTF_UINT32_DEC_WIDTH)); +#ifdef INT64_MAX + sprintf (str1, "%" PRINTF_INT64_MODIFIER "d", INT64_C(9223372036854775807)); + if (0 != strcmp (str1, "9223372036854775807")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str1)); + if (atoi(PRINTF_INT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR (("Something wrong with PRINTF_INT64_DEC_WIDTH : %s, %d\n", PRINTF_INT64_DEC_WIDTH, (int) strlen(str1))); + sprintf (str1, "%" PRINTF_INT64_MODIFIER "u", UINT64_C(18446744073709550591)); + if (0 != strcmp (str1, "18446744073709550591")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str1)); + if (atoi(PRINTF_UINT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR (("Something wrong with PRINTF_UINT64_DEC_WIDTH : %s, %d\n", PRINTF_UINT64_DEC_WIDTH, (int) strlen(str1))); +#endif + + sprintf (str0, "%d %x\n", 0, ~0); + + sprintf (str1, "%d %x\n", i8, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i8 : %s\n", str1)); + sprintf (str1, "%u %x\n", u8, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u8 : %s\n", str1)); + sprintf (str1, "%d %x\n", i16, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i16 : %s\n", str1)); + sprintf (str1, "%u %x\n", u16, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u16 : %s\n", str1)); + sprintf (str1, "%" PRINTF_INT32_MODIFIER "d %x\n", i32, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i32 : %s\n", str1)); + sprintf (str1, "%" PRINTF_INT32_MODIFIER "u %x\n", u32, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u32 : %s\n", str1)); +#ifdef INT64_MAX + sprintf (str1, "%" PRINTF_INT64_MODIFIER "d %x\n", i64, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i64 : %s\n", str1)); +#endif + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "d %x\n", imax, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with imax : %s\n", str1)); + sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "u %x\n", umax, ~0); + if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with umax : %s\n", str1)); + + TESTUMAX(8); + TESTUMAX(16); + TESTUMAX(32); +#ifdef INT64_MAX + TESTUMAX(64); +#endif + +#define STR(v) #v +#define Q(v) printf ("sizeof " STR(v) " = %u\n", (unsigned) sizeof (v)); + if (err_n) { + printf ("pstdint.h is not correct. Please use sizes below to correct it:\n"); + } + + Q(int) + Q(unsigned) + Q(long int) + Q(short int) + Q(int8_t) + Q(int16_t) + Q(int32_t) +#ifdef INT64_MAX + Q(int64_t) +#endif + + return EXIT_SUCCESS; +} + +#endif diff --git a/Source/library/FBSUtil/flatcc/portable/punaligned.h b/Source/library/FBSUtil/flatcc/portable/punaligned.h new file mode 100644 index 0000000..6ed1005 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/punaligned.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2016 Mikkel Fahnøe Jørgensen, dvide.com + * + * (MIT License) + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#ifndef PUNLIGNED_H +#define PUNLIGNED_H + +#ifndef PORTABLE_UNALIGNED_ACCESS + +#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) +#define PORTABLE_UNALIGNED_ACCESS 1 +#else +#define PORTABLE_UNALIGNED_ACCESS 0 +#endif + +#endif + +/* `unaligned_read_16` might not be defined if endianness was not determined. */ +#if !defined(unaligned_read_le16toh) + +#include "pendian.h" + +#ifndef UINT8_MAX +#include +#endif + +#if PORTABLE_UNALIGNED_ACCESS + +#define unaligned_read_16(p) (*(uint16_t*)(p)) +#define unaligned_read_32(p) (*(uint32_t*)(p)) +#define unaligned_read_64(p) (*(uint64_t*)(p)) + +#define unaligned_read_le16toh(p) le16toh(*(uint16_t*)(p)) +#define unaligned_read_le32toh(p) le32toh(*(uint32_t*)(p)) +#define unaligned_read_le64toh(p) le64toh(*(uint64_t*)(p)) + +#define unaligned_read_be16toh(p) be16toh(*(uint16_t*)(p)) +#define unaligned_read_be32toh(p) be32toh(*(uint32_t*)(p)) +#define unaligned_read_be64toh(p) be64toh(*(uint64_t*)(p)) + +#define unaligned_write_16(p, v) (*(uint16_t*)(p) = (uint16_t)(v)) +#define unaligned_write_32(p, v) (*(uint32_t*)(p) = (uint32_t)(v)) +#define unaligned_write_64(p, v) (*(uint64_t*)(p) = (uint64_t)(v)) + +#define unaligned_write_htole16(p, v) (*(uint16_t*)(p) = htole16(v)) +#define unaligned_write_htole32(p, v) (*(uint32_t*)(p) = htole32(v)) +#define unaligned_write_htole64(p, v) (*(uint64_t*)(p) = htole64(v)) + +#define unaligned_write_htobe16(p, v) (*(uint16_t*)(p) = htobe16(v)) +#define unaligned_write_htobe32(p, v) (*(uint32_t*)(p) = htobe32(v)) +#define unaligned_write_htobe64(p, v) (*(uint64_t*)(p) = htobe64(v)) + +#else + +#define unaligned_read_le16toh(p) ( \ + (((uint16_t)(((uint8_t *)(p))[0])) << 0) | \ + (((uint16_t)(((uint8_t *)(p))[1])) << 8)) + +#define unaligned_read_le32toh(p) ( \ + (((uint32_t)(((uint8_t *)(p))[0])) << 0) | \ + (((uint32_t)(((uint8_t *)(p))[1])) << 8) | \ + (((uint32_t)(((uint8_t *)(p))[2])) << 16) | \ + (((uint32_t)(((uint8_t *)(p))[3])) << 24)) + +#define unaligned_read_le64toh(p) ( \ + (((uint64_t)(((uint8_t *)(p))[0])) << 0) | \ + (((uint64_t)(((uint8_t *)(p))[1])) << 8) | \ + (((uint64_t)(((uint8_t *)(p))[2])) << 16) | \ + (((uint64_t)(((uint8_t *)(p))[3])) << 24) | \ + (((uint64_t)(((uint8_t *)(p))[4])) << 32) | \ + (((uint64_t)(((uint8_t *)(p))[5])) << 40) | \ + (((uint64_t)(((uint8_t *)(p))[6])) << 48) | \ + (((uint64_t)(((uint8_t *)(p))[7])) << 56)) + +#define unaligned_read_be16toh(p) ( \ + (((uint16_t)(((uint8_t *)(p))[0])) << 8) | \ + (((uint16_t)(((uint8_t *)(p))[1])) << 0)) + +#define unaligned_read_be32toh(p) ( \ + (((uint32_t)(((uint8_t *)(p))[0])) << 24) | \ + (((uint32_t)(((uint8_t *)(p))[1])) << 16) | \ + (((uint32_t)(((uint8_t *)(p))[2])) << 8) | \ + (((uint32_t)(((uint8_t *)(p))[3])) << 0)) + +#define unaligned_read_be64toh(p) ( \ + (((uint64_t)(((uint8_t *)(p))[0])) << 56) | \ + (((uint64_t)(((uint8_t *)(p))[1])) << 48) | \ + (((uint64_t)(((uint8_t *)(p))[2])) << 40) | \ + (((uint64_t)(((uint8_t *)(p))[3])) << 32) | \ + (((uint64_t)(((uint8_t *)(p))[4])) << 24) | \ + (((uint64_t)(((uint8_t *)(p))[5])) << 16) | \ + (((uint64_t)(((uint8_t *)(p))[6])) << 8) | \ + (((uint64_t)(((uint8_t *)(p))[7])) << 0)) + +#define unaligned_write_htole16(p, v) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint16_t)(v)) >> 0); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint16_t)(v)) >> 8); \ + } while (0) + +#define unaligned_write_htole32(p, v) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint32_t)(v)) >> 0); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint32_t)(v)) >> 8); \ + ((uint8_t *)(p))[2] = (uint8_t)(((uint32_t)(v)) >> 16); \ + ((uint8_t *)(p))[3] = (uint8_t)(((uint32_t)(v)) >> 24); \ + } while (0) + +#define unaligned_write_htole64(p) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint64_t)(v)) >> 0); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint64_t)(v)) >> 8); \ + ((uint8_t *)(p))[2] = (uint8_t)(((uint64_t)(v)) >> 16); \ + ((uint8_t *)(p))[3] = (uint8_t)(((uint64_t)(v)) >> 24); \ + ((uint8_t *)(p))[4] = (uint8_t)(((uint64_t)(v)) >> 32); \ + ((uint8_t *)(p))[5] = (uint8_t)(((uint64_t)(v)) >> 40); \ + ((uint8_t *)(p))[6] = (uint8_t)(((uint64_t)(v)) >> 48); \ + ((uint8_t *)(p))[7] = (uint8_t)(((uint64_t)(v)) >> 56); \ + } while (0) + +#define unaligned_write_htobe16(p, v) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint16_t)(v)) >> 8); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint16_t)(v)) >> 0); \ + } while (0) + +#define unaligned_write_htobe32(p, v) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint32_t)(v)) >> 24); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint32_t)(v)) >> 16); \ + ((uint8_t *)(p))[2] = (uint8_t)(((uint32_t)(v)) >> 8); \ + ((uint8_t *)(p))[3] = (uint8_t)(((uint32_t)(v)) >> 0); \ + } while (0) + +#define unaligned_write_htobe64(p) do { \ + ((uint8_t *)(p))[0] = (uint8_t)(((uint64_t)(v)) >> 56); \ + ((uint8_t *)(p))[1] = (uint8_t)(((uint64_t)(v)) >> 48); \ + ((uint8_t *)(p))[2] = (uint8_t)(((uint64_t)(v)) >> 40); \ + ((uint8_t *)(p))[3] = (uint8_t)(((uint64_t)(v)) >> 32); \ + ((uint8_t *)(p))[4] = (uint8_t)(((uint64_t)(v)) >> 24); \ + ((uint8_t *)(p))[5] = (uint8_t)(((uint64_t)(v)) >> 16); \ + ((uint8_t *)(p))[6] = (uint8_t)(((uint64_t)(v)) >> 8); \ + ((uint8_t *)(p))[7] = (uint8_t)(((uint64_t)(v)) >> 0); \ + } while (0) + +#if __LITTLE_ENDIAN__ +#define unaligned_read_16(p) unaligned_read_le16toh(p) +#define unaligned_read_32(p) unaligned_read_le32toh(p) +#define unaligned_read_64(p) unaligned_read_le64toh(p) + +#define unaligned_write_16(p) unaligned_write_htole16(p) +#define unaligned_write_32(p) unaligned_write_htole32(p) +#define unaligned_write_64(p) unaligned_write_htole64(p) +#endif + +#if __BIG_ENDIAN__ +#define unaligned_read_16(p) unaligned_read_be16toh(p) +#define unaligned_read_32(p) unaligned_read_be32toh(p) +#define unaligned_read_64(p) unaligned_read_be64toh(p) + +#define unaligned_write_16(p) unaligned_write_htobe16(p) +#define unaligned_write_32(p) unaligned_write_htobe32(p) +#define unaligned_write_64(p) unaligned_write_htobe64(p) +#endif + +#endif + +#endif + +#endif /* PUNALIGNED_H */ diff --git a/Source/library/FBSUtil/flatcc/portable/pversion.h b/Source/library/FBSUtil/flatcc/portable/pversion.h new file mode 100644 index 0000000..48508cb --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pversion.h @@ -0,0 +1,6 @@ +#define PORTABLE_VERSION_TEXT "0.2.2" +#define PORTABLE_VERSION_MAJOR 0 +#define PORTABLE_VERSION_MINOR 2 +#define PORTABLE_VERSION_PATCH 2 +/* 1 or 0 */ +#define PORTABLE_VERSION_RELEASED 1 diff --git a/Source/library/FBSUtil/flatcc/portable/pwarnings.h b/Source/library/FBSUtil/flatcc/portable/pwarnings.h new file mode 100644 index 0000000..8088712 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/portable/pwarnings.h @@ -0,0 +1,44 @@ +#ifndef PWARNINGS_H +#define PWARNINGS_H + +/* + * See also pdiagnostics.h headers for per file control of common + * warnings. + * + * This file is intended for global disabling of warnings that shouldn't + * be present in C11 or perhaps C99, or a generally just noise where + * recent clang / gcc compile cleanly with high warning levels. + */ + +#if defined(_MSC_VER) +/* Needed when flagging code in or out and more. */ +#pragma warning(disable: 4127) /* conditional expression is constant */ +/* happens also in MS's own headers. */ +#pragma warning(disable: 4668) /* preprocessor name not defined */ +/* MSVC does not respect double parenthesis for intent */ +#pragma warning(disable: 4706) /* assignment within conditional expression */ +/* `inline` only advisory anyway. */ +#pragma warning(disable: 4710) /* function not inlined */ +/* Well, we don't intend to add the padding manually. */ +#pragma warning(disable: 4820) /* x bytes padding added in struct */ + +/* + * Don't warn that fopen etc. are unsafe + * + * Define a compiler flag like `-D_CRT_SECURE_NO_WARNINGS` in the build. + * For some reason it doesn't work when defined here. + * + * #define _CRT_SECURE_NO_WARNINGS + */ + +/* + * Anonymous union in struct is valid in C11 and has been supported in + * GCC and Clang for a while, but it is not C99. MSVC also handles it, + * but warns. Truly portable code should perhaps not use this feature, + * but this is not the place to complain about it. + */ +#pragma warning(disable: 4201) /* nonstandard extension used: nameless struct/union */ + +#endif /* _MSV_VER */ + +#endif /* PWARNINGS_H */ diff --git a/Source/library/FBSUtil/flatcc/reflection/README.md b/Source/library/FBSUtil/flatcc/reflection/README.md new file mode 100644 index 0000000..29bc096 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/README.md @@ -0,0 +1,3 @@ +Generated by flatcc + +Keep checked in - needed by flatcc to generate binary schema. diff --git a/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_builder.h b/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_builder.h new file mode 100644 index 0000000..fefd7dd --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_builder.h @@ -0,0 +1,467 @@ +#ifndef FLATBUFFERS_COMMON_BUILDER_H +#define FLATBUFFERS_COMMON_BUILDER_H + +/* Generated by flatcc 0.4.2 FlatBuffers schema compiler for C by dvide.com */ + +/* Common FlatBuffers build functionality for C. */ + +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" +#ifndef FLATBUILDER_H +#include "flatcc/flatcc_builder.h" +#endif +typedef flatcc_builder_t flatbuffers_builder_t; +typedef flatcc_builder_ref_t flatbuffers_ref_t; +typedef flatcc_builder_ref_t flatbuffers_vec_ref_t; +/* integer return code (ref and ptr always fail on 0) */ +#define flatbuffers_failed(x) ((x) < 0) +typedef flatbuffers_ref_t flatbuffers_root_t; +#define flatbuffers_root(ref) ((flatbuffers_root_t)(ref)) + +#define __flatbuffers_build_buffer(NS)\ +typedef NS ## ref_t NS ## buffer_ref_t;\ +static inline int NS ## buffer_start(NS ## builder_t *B, NS ##fid_t fid)\ +{ return flatcc_builder_start_buffer(B, fid, 0, 0); }\ +static inline int NS ## buffer_start_with_size(NS ## builder_t *B, NS ##fid_t fid)\ +{ return flatcc_builder_start_buffer(B, fid, 0, flatcc_builder_with_size); }\ +static inline int NS ## buffer_start_aligned(NS ## builder_t *B, NS ##fid_t fid, uint16_t block_align)\ +{ return flatcc_builder_start_buffer(B, fid, block_align, 0); }\ +static inline int NS ## buffer_start_aligned_with_size(NS ## builder_t *B, NS ##fid_t fid, uint16_t block_align)\ +{ return flatcc_builder_start_buffer(B, fid, block_align, flatcc_builder_with_size); }\ +static inline NS ## buffer_ref_t NS ## buffer_end(NS ## builder_t *B, NS ## ref_t root)\ +{ return flatcc_builder_end_buffer(B, root); } + +#define __flatbuffers_build_table_root(NS, N, FID, TFID)\ +static inline int N ## _start_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, FID) ? -1 : N ## _start(B); }\ +static inline int N ## _start_as_root_with_size(NS ## builder_t *B)\ +{ return NS ## buffer_start_with_size(B, FID) ? -1 : N ## _start(B); }\ +static inline int N ## _start_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, TFID) ? -1 : N ## _start(B); }\ +static inline int N ## _start_as_typed_root_with_size(NS ## builder_t *B)\ +{ return NS ## buffer_start_with_size(B, TFID) ? -1 : N ## _start(B); }\ +static inline NS ## buffer_ref_t N ## _end_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end(B)); }\ +static inline NS ## buffer_ref_t N ## _end_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end(B)); }\ +static inline NS ## buffer_ref_t N ## _create_as_root(NS ## builder_t *B __ ## N ## _formal_args)\ +{ if (NS ## buffer_start(B, FID)) return 0; return NS ## buffer_end(B, N ## _create(B __ ## N ## _call_args)); }\ +static inline NS ## buffer_ref_t N ## _create_as_root_with_size(NS ## builder_t *B __ ## N ## _formal_args)\ +{ if (NS ## buffer_start_with_size(B, FID)) return 0; return NS ## buffer_end(B, N ## _create(B __ ## N ## _call_args)); }\ +static inline NS ## buffer_ref_t N ## _create_as_typed_root(NS ## builder_t *B __ ## N ## _formal_args)\ +{ if (NS ## buffer_start(B, TFID)) return 0; return NS ## buffer_end(B, N ## _create(B __ ## N ## _call_args)); }\ +static inline NS ## buffer_ref_t N ## _create_as_typed_root_with_size(NS ## builder_t *B __ ## N ## _formal_args)\ +{ if (NS ## buffer_start_with_size(B, TFID)) return 0; return NS ## buffer_end(B, N ## _create(B __ ## N ## _call_args)); } + +#define __flatbuffers_build_table_prolog(NS, N, FID, TFID)\ +__flatbuffers_build_table_vector_ops(NS, N ## _vec, N)\ +__flatbuffers_build_table_root(NS, N, FID, TFID) + +#define __flatbuffers_build_struct_root(NS, N, A, FID, TFID)\ +static inline N ## _t *N ## _start_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, FID) ? 0 : N ## _start(B); }\ +static inline N ## _t *N ## _start_as_root_with_size(NS ## builder_t *B)\ +{ return NS ## buffer_start_with_size(B, FID) ? 0 : N ## _start(B); }\ +static inline N ## _t *N ## _start_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, TFID) ? 0 : N ## _start(B); }\ +static inline N ## _t *N ## _start_as_typed_root_with_size(NS ## builder_t *B)\ +{ return NS ## buffer_start_with_size(B, TFID) ? 0 : N ## _start(B); }\ +static inline NS ## buffer_ref_t N ## _end_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end(B)); }\ +static inline NS ## buffer_ref_t N ## _end_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end(B)); }\ +static inline NS ## buffer_ref_t N ## _end_pe_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end_pe(B)); }\ +static inline NS ## buffer_ref_t N ## _end_pe_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_end(B, N ## _end_pe(B)); }\ +static inline NS ## buffer_ref_t N ## _create_as_root(NS ## builder_t *B __ ## N ## _formal_args)\ +{ return flatcc_builder_create_buffer(B, FID, 0,\ + N ## _create(B __ ## N ## _call_args), A, 0); }\ +static inline NS ## buffer_ref_t N ## _create_as_root_with_size(NS ## builder_t *B __ ## N ## _formal_args)\ +{ return flatcc_builder_create_buffer(B, FID, 0,\ + N ## _create(B __ ## N ## _call_args), A, flatcc_builder_with_size); }\ +static inline NS ## buffer_ref_t N ## _create_as_typed_root(NS ## builder_t *B __ ## N ## _formal_args)\ +{ return flatcc_builder_create_buffer(B, TFID, 0,\ + N ## _create(B __ ## N ## _call_args), A, 0); }\ +static inline NS ## buffer_ref_t N ## _create_as_typed_root_with_size(NS ## builder_t *B __ ## N ## _formal_args)\ +{ return flatcc_builder_create_buffer(B, TFID, 0,\ + N ## _create(B __ ## N ## _call_args), A, flatcc_builder_with_size); } + +#define __flatbuffers_build_nested_table_root(NS, N, TN, FID, TFID)\ +static inline int N ## _start_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, FID) ? -1 : TN ## _start(B); }\ +static inline int N ## _start_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, TFID) ? -1 : TN ## _start(B); }\ +static inline int N ## _end_as_root(NS ## builder_t *B)\ +{ return N ## _add(B, NS ## buffer_end(B, TN ## _end(B))); }\ +static inline int N ## _end_as_typed_root(NS ## builder_t *B)\ +{ return N ## _add(B, NS ## buffer_end(B, TN ## _end(B))); }\ +static inline int N ## _nest(NS ## builder_t *B, void *data, size_t size, uint16_t align)\ +{ if (NS ## buffer_start(B, FID)) return -1;\ + return N ## _add(B, NS ## buffer_end(B, flatcc_builder_create_vector(B, data, size, 1,\ + align ? align : 8, FLATBUFFERS_COUNT_MAX(1)))); }\ +static inline int N ## _typed_nest(NS ## builder_t *B, void *data, size_t size, uint16_t align)\ +{ if (NS ## buffer_start(B, TFID)) return -1;\ + return N ## _add(B, NS ## buffer_end(B, flatcc_builder_create_vector(B, data, size, 1,\ + align ? align : 8, FLATBUFFERS_COUNT_MAX(1)))); } + +#define __flatbuffers_build_nested_struct_root(NS, N, TN, A, FID, TFID)\ +static inline TN ## _t *N ## _start_as_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, FID) ? 0 : TN ## _start(B); }\ +static inline TN ## _t *N ## _start_as_typed_root(NS ## builder_t *B)\ +{ return NS ## buffer_start(B, FID) ? 0 : TN ## _start(B); }\ +static inline int N ## _end_as_root(NS ## builder_t *B)\ +{ return N ## _add(B, NS ## buffer_end(B, TN ## _end(B))); }\ +static inline int N ## _end_as_typed_root(NS ## builder_t *B)\ +{ return N ## _add(B, NS ## buffer_end(B, TN ## _end(B))); }\ +static inline int N ## _end_pe_as_root(NS ## builder_t *B)\ +{ return N ## _add(B, NS ## buffer_end(B, TN ## _end_pe(B))); }\ +static inline int N ## _create_as_root(NS ## builder_t *B __ ## TN ## _formal_args)\ +{ return N ## _add(B, flatcc_builder_create_buffer(B, FID, 0,\ + TN ## _create(B __ ## TN ## _call_args), A, flatcc_builder_is_nested)); }\ +static inline int N ## _create_as_typed_root(NS ## builder_t *B __ ## TN ## _formal_args)\ +{ return N ## _add(B, flatcc_builder_create_buffer(B, TFID, 0,\ + TN ## _create(B __ ## TN ## _call_args), A, flatcc_builder_is_nested)); }\ +static inline int N ## _nest(NS ## builder_t *B, void *data, size_t size, uint16_t align)\ +{ if (NS ## buffer_start(B, FID)) return -1;\ + return N ## _add(B, NS ## buffer_end(B, flatcc_builder_create_vector(B, data, size, 1,\ + align < A ? A : align, FLATBUFFERS_COUNT_MAX(1)))); }\ +static inline int N ## _typed_nest(NS ## builder_t *B, void *data, size_t size, uint16_t align)\ +{ if (NS ## buffer_start(B, TFID)) return -1;\ + return N ## _add(B, NS ## buffer_end(B, flatcc_builder_create_vector(B, data, size, 1,\ + align < A ? A : align, FLATBUFFERS_COUNT_MAX(1)))); } + +#define __flatbuffers_build_vector_ops(NS, V, N, TN, T)\ +static inline T *V ## _extend(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_extend_vector(B, len); }\ +static inline T *V ## _append(NS ## builder_t *B, const T *data, size_t len)\ +{ return flatcc_builder_append_vector(B, data, len); }\ +static inline int V ## _truncate(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_truncate_vector(B, len); }\ +static inline T *V ## _edit(NS ## builder_t *B)\ +{ return flatcc_builder_vector_edit(B); }\ +static inline size_t V ## _reserved_len(NS ## builder_t *B)\ +{ return flatcc_builder_vector_count(B); }\ +static inline T *V ## _push(NS ## builder_t *B, const T *p)\ +{ T *_p; return (_p = flatcc_builder_extend_vector(B, 1)) ? (memcpy(_p, p, TN ## __size()), _p) : 0; }\ +static inline T *V ## _push_copy(NS ## builder_t *B, const T *p)\ +{ T *_p; return (_p = flatcc_builder_extend_vector(B, 1)) ? TN ## _copy(_p, p) : 0; }\ +static inline T *V ## _push_create(NS ## builder_t *B __ ## TN ## _formal_args)\ +{ T *_p; return (_p = flatcc_builder_extend_vector(B, 1)) ? TN ## _assign(_p __ ## TN ## _call_args) : 0; } + +#define __flatbuffers_build_vector(NS, N, T, S, A)\ +typedef NS ## ref_t N ## _vec_ref_t;\ +static inline int N ## _vec_start(NS ## builder_t *B)\ +{ return flatcc_builder_start_vector(B, S, A, FLATBUFFERS_COUNT_MAX(S)); }\ +static inline N ## _vec_ref_t N ## _vec_end_pe(NS ## builder_t *B)\ +{ return flatcc_builder_end_vector(B); }\ +static inline N ## _vec_ref_t N ## _vec_end(NS ## builder_t *B)\ +{ if (!NS ## is_native_pe()) { size_t i, n; T *p = flatcc_builder_vector_edit(B);\ + for (i = 0, n = flatcc_builder_vector_count(B); i < n; ++i)\ + { N ## _to_pe(N ## __ptr_add(p, i)); }} return flatcc_builder_end_vector(B); }\ +static inline N ## _vec_ref_t N ## _vec_create_pe(NS ## builder_t *B, const T *data, size_t len)\ +{ return flatcc_builder_create_vector(B, data, len, S, A, FLATBUFFERS_COUNT_MAX(S)); }\ +static inline N ## _vec_ref_t N ## _vec_create(NS ## builder_t *B, const T *data, size_t len)\ +{ if (!NS ## is_native_pe()) { size_t i; T *p; int ret = flatcc_builder_start_vector(B, S, A, FLATBUFFERS_COUNT_MAX(S)); if (ret) { return ret; }\ + p = flatcc_builder_extend_vector(B, len); if (!p) return 0;\ + for (i = 0; i < len; ++i) { N ## _copy_to_pe(N ## __ptr_add(p, i), N ## __const_ptr_add(data, i)); }\ + return flatcc_builder_end_vector(B); } else return flatcc_builder_create_vector(B, data, len, S, A, FLATBUFFERS_COUNT_MAX(S)); }\ +static inline N ## _vec_ref_t N ## _vec_clone(NS ## builder_t *B, N ##_vec_t vec)\ +{ return flatcc_builder_create_vector(B, vec, N ## _vec_len(vec), S, A, FLATBUFFERS_COUNT_MAX(S)); }\ +static inline N ## _vec_ref_t N ## _vec_slice(NS ## builder_t *B, N ##_vec_t vec, size_t index, size_t len)\ +{ size_t n = N ## _vec_len(vec); if (index >= n) index = n; n -= index; if (len > n) len = n;\ + return flatcc_builder_create_vector(B, N ## __const_ptr_add(vec, index), len, S, A, FLATBUFFERS_COUNT_MAX(S)); }\ +__flatbuffers_build_vector_ops(NS, N ## _vec, N, N, T) + +#define __flatbuffers_build_string_vector_ops(NS, N)\ +static inline int N ## _push_start(NS ## builder_t *B)\ +{ return NS ## string_start(B); }\ +static inline NS ## string_ref_t *N ## _push_end(NS ## builder_t *B)\ +{ return NS ## string_vec_push(B, NS ## string_end(B)); }\ +static inline NS ## string_ref_t *N ## _push_create(NS ## builder_t *B, const char *s, size_t len)\ +{ return NS ## string_vec_push(B, NS ## string_create(B, s, len)); }\ +static inline NS ## string_ref_t *N ## _push_create_str(NS ## builder_t *B, const char *s)\ +{ return NS ## string_vec_push(B, NS ## string_create_str(B, s)); }\ +static inline NS ## string_ref_t *N ## _push_create_strn(NS ## builder_t *B, const char *s, size_t max_len)\ +{ return NS ## string_vec_push(B, NS ## string_create_strn(B, s, max_len)); }\ +static inline NS ## string_ref_t *N ## _push_clone(NS ## builder_t *B, NS ## string_t string)\ +{ return NS ## string_vec_push(B, NS ## string_clone(B, string)); }\ +static inline NS ## string_ref_t *N ## _push_slice(NS ## builder_t *B, NS ## string_t string, size_t index, size_t len)\ +{ return NS ## string_vec_push(B, NS ## string_slice(B, string, index, len)); } + +#define __flatbuffers_build_table_vector_ops(NS, N, TN)\ +static inline int N ## _push_start(NS ## builder_t *B)\ +{ return TN ## _start(B); }\ +static inline TN ## _ref_t *N ## _push_end(NS ## builder_t *B)\ +{ return N ## _push(B, TN ## _end(B)); }\ +static inline TN ## _ref_t *N ## _push_create(NS ## builder_t *B __ ## TN ##_formal_args)\ +{ return N ## _push(B, TN ## _create(B __ ## TN ## _call_args)); } + +#define __flatbuffers_build_offset_vector_ops(NS, V, N, TN)\ +static inline TN ## _ref_t *V ## _extend(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_extend_offset_vector(B, len); }\ +static inline TN ## _ref_t *V ## _append(NS ## builder_t *B, const TN ## _ref_t *data, size_t len)\ +{ return flatcc_builder_append_offset_vector(B, data, len); }\ +static inline int V ## _truncate(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_truncate_offset_vector(B, len); }\ +static inline TN ## _ref_t *V ## _edit(NS ## builder_t *B)\ +{ return flatcc_builder_offset_vector_edit(B); }\ +static inline size_t V ## _reserved_len(NS ## builder_t *B)\ +{ return flatcc_builder_offset_vector_count(B); }\ +static inline TN ## _ref_t *V ## _push(NS ## builder_t *B, const TN ## _ref_t ref)\ +{ return ref ? flatcc_builder_offset_vector_push(B, ref) : 0; } + +#define __flatbuffers_build_offset_vector(NS, N)\ +typedef NS ## ref_t N ## _vec_ref_t;\ +static inline int N ## _vec_start(NS ## builder_t *B)\ +{ return flatcc_builder_start_offset_vector(B); }\ +static inline N ## _vec_ref_t N ## _vec_end(NS ## builder_t *B)\ +{ return flatcc_builder_end_offset_vector(B); }\ +static inline N ## _vec_ref_t N ## _vec_create(NS ## builder_t *B, const N ## _ref_t *data, size_t len)\ +{ return flatcc_builder_create_offset_vector(B, data, len); }\ +__flatbuffers_build_offset_vector_ops(NS, N ## _vec, N, N) + +#define __flatbuffers_build_string_ops(NS, N)\ +static inline char *N ## _append(NS ## builder_t *B, const char *s, size_t len)\ +{ return flatcc_builder_append_string(B, s, len); }\ +static inline char *N ## _append_str(NS ## builder_t *B, const char *s)\ +{ return flatcc_builder_append_string_str(B, s); }\ +static inline char *N ## _append_strn(NS ## builder_t *B, const char *s, size_t len)\ +{ return flatcc_builder_append_string_strn(B, s, len); }\ +static inline size_t N ## _reserved_len(NS ## builder_t *B)\ +{ return flatcc_builder_string_len(B); }\ +static inline char *N ## _extend(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_extend_string(B, len); }\ +static inline char *N ## _edit(NS ## builder_t *B)\ +{ return flatcc_builder_string_edit(B); }\ +static inline int N ## _truncate(NS ## builder_t *B, size_t len)\ +{ return flatcc_builder_truncate_string(B, len); } + +#define __flatbuffers_build_string(NS)\ +typedef NS ## ref_t NS ## string_ref_t;\ +static inline int NS ## string_start(NS ## builder_t *B)\ +{ return flatcc_builder_start_string(B); }\ +static inline NS ## string_ref_t NS ## string_end(NS ## builder_t *B)\ +{ return flatcc_builder_end_string(B); }\ +static inline NS ## ref_t NS ## string_create(NS ## builder_t *B, const char *s, size_t len)\ +{ return flatcc_builder_create_string(B, s, len); }\ +static inline NS ## ref_t NS ## string_create_str(NS ## builder_t *B, const char *s)\ +{ return flatcc_builder_create_string_str(B, s); }\ +static inline NS ## ref_t NS ## string_create_strn(NS ## builder_t *B, const char *s, size_t len)\ +{ return flatcc_builder_create_string_strn(B, s, len); }\ +static inline NS ## string_ref_t NS ## string_clone(NS ## builder_t *B, NS ## string_t string)\ +{ return flatcc_builder_create_string(B, string, NS ## string_len(string)); }\ +static inline NS ## string_ref_t NS ## string_slice(NS ## builder_t *B, NS ## string_t string, size_t index, size_t len)\ +{ size_t n = NS ## string_len(string); if (index >= n) index = n; n -= index; if (len > n) len = n;\ + return flatcc_builder_create_string(B, string + index, len); }\ +__flatbuffers_build_string_ops(NS, NS ## string)\ +__flatbuffers_build_offset_vector(NS, NS ## string) + +#define __flatbuffers_copy_from_pe(P, P2, N) (*(P) = N ## _cast_from_pe(*P2), (P)) +#define __flatbuffers_from_pe(P, N) (*(P) = N ## _cast_from_pe(*P), (P)) +#define __flatbuffers_copy_to_pe(P, P2, N) (*(P) = N ## _cast_to_pe(*P2), (P)) +#define __flatbuffers_to_pe(P, N) (*(P) = N ## _cast_to_pe(*P), (P)) +#define __flatbuffers_define_scalar_primitives(NS, N, T)\ +static inline T *N ## _from_pe(T *p) { return __ ## NS ## from_pe(p, N); }\ +static inline T *N ## _to_pe(T *p) { return __ ## NS ## to_pe(p, N); }\ +static inline T *N ## _copy(T *p, const T *p2) { *p = *p2; return p; }\ +static inline T *N ## _copy_from_pe(T *p, const T *p2)\ +{ return __ ## NS ## copy_from_pe(p, p2, N); }\ +static inline T *N ## _copy_to_pe(T *p, const T *p2) \ +{ return __ ## NS ## copy_to_pe(p, p2, N); }\ +static inline T *N ## _assign(T *p, const T v0) { *p = v0; return p; }\ +static inline T *N ## _assign_from_pe(T *p, T v0)\ +{ *p = N ## _cast_from_pe(v0); return p; }\ +static inline T *N ## _assign_to_pe(T *p, T v0)\ +{ *p = N ## _cast_to_pe(v0); return p; } +#define __flatbuffers_build_scalar(NS, N, T)\ +__ ## NS ## define_scalar_primitives(NS, N, T)\ +__ ## NS ## build_vector(NS, N, T, sizeof(T), sizeof(T)) +/* Depends on generated copy_to/from_pe functions, and the type. */ +#define __flatbuffers_define_struct_primitives(NS, N)\ +static inline N ## _t *N ##_to_pe(N ## _t *p)\ +{ if (!NS ## is_native_pe()) { N ## _copy_to_pe(p, p); }; return p; }\ +static inline N ## _t *N ##_from_pe(N ## _t *p)\ +{ if (!NS ## is_native_pe()) { N ## _copy_from_pe(p, p); }; return p; }\ +static inline N ## _t *N ## _clear(N ## _t *p) { return memset(p, 0, N ## __size()); } + +/* Depends on generated copy/assign_to/from_pe functions, and the type. */ +#define __flatbuffers_build_struct(NS, N, S, A, FID, TFID)\ +__ ## NS ## define_struct_primitives(NS, N)\ +typedef NS ## ref_t N ## _ref_t;\ +static inline N ## _t *N ## _start(NS ## builder_t *B)\ +{ return flatcc_builder_start_struct(B, S, A); }\ +static inline N ## _ref_t N ## _end(NS ## builder_t *B)\ +{ if (!NS ## is_native_pe()) { N ## _to_pe(flatcc_builder_struct_edit(B)); }\ + return flatcc_builder_end_struct(B); }\ +static inline N ## _ref_t N ## _end_pe(NS ## builder_t *B)\ +{ return flatcc_builder_end_struct(B); }\ +static inline N ## _ref_t N ## _create(NS ## builder_t *B __ ## N ## _formal_args)\ +{ N ## _t *_p = N ## _start(B); if (!_p) return 0; N ##_assign_to_pe(_p __ ## N ## _call_args);\ + return N ## _end_pe(B); }\ +__flatbuffers_build_vector(NS, N, N ## _t, S, A)\ +__flatbuffers_build_struct_root(NS, N, A, FID, TFID) + +#define __flatbuffers_build_table(NS, N, K)\ +typedef NS ## ref_t N ## _ref_t;\ +static inline int N ## _start(NS ## builder_t *B)\ +{ return flatcc_builder_start_table(B, K); }\ +static inline N ## _ref_t N ## _end(NS ## builder_t *B)\ +{ assert(flatcc_builder_check_required(B, __ ## N ## _required,\ + sizeof(__ ## N ## _required) / sizeof(__ ## N ## _required[0]) - 1));\ + return flatcc_builder_end_table(B); }\ +__flatbuffers_build_offset_vector(NS, N) + +#define __flatbuffers_build_table_field(ID, NS, N, TN)\ +static inline int N ## _add(NS ## builder_t *B, TN ## _ref_t ref)\ +{ TN ## _ref_t *_p; return (ref && (_p = flatcc_builder_table_add_offset(B, ID))) ?\ + ((*_p = ref), 0) : -1; }\ +static inline int N ## _start(NS ## builder_t *B)\ +{ return TN ## _start(B); }\ +static inline int N ## _end(NS ## builder_t *B)\ +{ return N ## _add(B, TN ## _end(B)); }\ +static inline TN ## _ref_t N ## _create(NS ## builder_t *B __ ## TN ##_formal_args)\ +{ return N ## _add(B, TN ## _create(B __ ## TN ## _call_args)); } + +#define __flatbuffers_build_union_field(ID, NS, N, TN)\ +static inline int N ## _add(NS ## builder_t *B, TN ## _union_ref_t uref)\ +{ NS ## ref_t *_p; TN ## _union_type_t *_pt; if (uref.type == TN ## _NONE) return 0; if (uref._member == 0) return -1;\ + if (!(_pt = flatcc_builder_table_add(B, ID - 1, sizeof(*_pt), sizeof(_pt))) ||\ + !(_p = flatcc_builder_table_add_offset(B, ID))) return -1; *_pt = uref.type; *_p = uref._member; return 0; }\ +static inline int N ## _add_type(NS ## builder_t *B, TN ## _union_type_t type)\ +{ TN ## _union_type_t *_pt; if (type == TN ## _NONE) return 0; return (_pt = flatcc_builder_table_add(B, ID - 1,\ + sizeof(*_pt), sizeof(*_pt))) ? ((*_pt = type), 0) : -1; }\ +static inline int N ## _add_member(NS ## builder_t *B, TN ## _union_ref_t uref)\ +{ NS ## ref_t *p; if (uref.type == TN ## _NONE) return 0; return (p = flatcc_builder_table_add_offset(B, ID)) ?\ + ((*p = uref._member), 0) : -1; } + +/* M is the union member name and T is its type, i.e. the qualified name. */ +#define __flatbuffers_build_union_member_field(NS, N, NU, M, T)\ +static inline int N ## _ ## M ## _add(NS ## builder_t *B, T ## _ref_t ref)\ +{ return N ## _add(B, NU ## _as_ ## M (ref)); }\ +static inline int N ## _ ## M ## _start(NS ## builder_t *B)\ +{ return T ## _start(B); }\ +static inline int N ## _ ## M ## _end(NS ## builder_t *B)\ +{ return N ## _ ## M ## _add(B, T ## _end(B)); } + +/* NS: common namespace, ID: table field id (not offset), TN: name of type T, + * S: sizeof of scalar type, A: alignment of type T, default value V of type T. */ +#define __flatbuffers_build_scalar_field(ID, NS, N, TN, T, S, A, V)\ +static inline int N ## _add(NS ## builder_t *B, const T v)\ +{ T *_p; if (v == V) return 0; if (!(_p = flatcc_builder_table_add(B, ID, S, A))) return -1;\ + TN ## _assign_to_pe(_p, v); return 0; }\ +static inline int N ## _force_add(NS ## builder_t *B, const T v)\ +{ T *_p; if (!(_p = flatcc_builder_table_add(B, ID, S, A))) return -1;\ + TN ## _assign_to_pe(_p, v); return 0; }\ + +#define __flatbuffers_build_struct_field(ID, NS, N, TN, S, A)\ +static inline TN ## _t *N ## _start(NS ## builder_t *B)\ +{ return flatcc_builder_table_add(B, ID, S, A); }\ +static inline int N ## _end(NS ## builder_t *B)\ +{ if (!NS ## is_native_pe()) { TN ## _to_pe(flatcc_builder_table_edit(B, S)); } return 0; }\ +static inline int N ## _end_pe(NS ## builder_t *B) { return 0; }\ +static inline int N ## _create(NS ## builder_t *B __ ## TN ## _formal_args)\ +{ TN ## _t *_p = N ## _start(B); if (!_p) return 0; TN ##_assign_to_pe(_p __ ## TN ## _call_args);\ + return 0; }\ +static inline int N ## _add(NS ## builder_t *B, const TN ## _t *p)\ +{ TN ## _t *_p = N ## _start(B); if (!_p) return -1; TN ##_copy_to_pe(_p, p); return 0; }\ +static inline int N ## _clone(NS ## builder_t *B, TN ## _struct_t p)\ +{ return 0 == flatcc_builder_table_add_copy(B, ID, p, S, A) ? -1 : 0; } + +#define __flatbuffers_build_vector_field(ID, NS, N, TN, T)\ +static inline int N ## _add(NS ## builder_t *B, TN ## _vec_ref_t ref)\ +{ TN ## _vec_ref_t *_p; return (ref && (_p = flatcc_builder_table_add_offset(B, ID))) ? ((*_p = ref), 0) : -1; }\ +static inline int N ## _start(NS ## builder_t *B)\ +{ return TN ## _vec_start(B); }\ +static inline int N ## _end_pe(NS ## builder_t *B)\ +{ return N ## _add(B, TN ## _vec_end_pe(B)); }\ +static inline int N ## _end(NS ## builder_t *B)\ +{ return N ## _add(B, TN ## _vec_end(B)); }\ +static inline int N ## _create_pe(NS ## builder_t *B, T *data, size_t len)\ +{ return N ## _add(B, TN ## _vec_create_pe(B, data, len)); }\ +static inline int N ## _create(NS ## builder_t *B, T *data, size_t len)\ +{ return N ## _add(B, TN ## _vec_create(B, data, len)); }\ +static inline int N ## _clone(NS ## builder_t *B, TN ## _vec_t vec)\ +{ return N ## _add(B, TN ## _vec_clone(B, vec)); }\ +static inline int N ## _slice(NS ## builder_t *B, TN ## _vec_t vec, size_t index, size_t len)\ +{ return N ## _add(B, TN ## _vec_slice(B, vec, index, len)); }\ +__flatbuffers_build_vector_ops(NS, N, N, TN, T) + +#define __flatbuffers_build_offset_vector_field(ID, NS, N, TN)\ +static inline int N ## _add(NS ## builder_t *B, TN ## _vec_ref_t ref)\ +{ TN ## _vec_ref_t *_p; return (ref && (_p = flatcc_builder_table_add_offset(B, ID))) ? ((*_p = ref), 0) : -1; }\ +static inline int N ## _start(NS ## builder_t *B)\ +{ return flatcc_builder_start_offset_vector(B); }\ +static inline int N ## _end(NS ## builder_t *B)\ +{ return N ## _add(B, flatcc_builder_end_offset_vector(B)); }\ +static inline int N ## _create(NS ## builder_t *B, const TN ## _ref_t *data, size_t len)\ +{ return N ## _add(B, flatcc_builder_create_offset_vector(B, data, len)); }\ +__flatbuffers_build_offset_vector_ops(NS, N, N, TN) + +#define __flatbuffers_build_string_field(ID, NS, N)\ +static inline int N ## _add(NS ## builder_t *B, NS ## string_ref_t ref)\ +{ NS ## string_ref_t *_p; return (ref && (_p = flatcc_builder_table_add_offset(B, ID))) ? ((*_p = ref), 0) : -1; }\ +static inline int N ## _start(NS ## builder_t *B)\ +{ return flatcc_builder_start_string(B); }\ +static inline int N ## _end(NS ## builder_t *B)\ +{ return N ## _add(B, flatcc_builder_end_string(B)); }\ +static inline int N ## _create(NS ## builder_t *B, const char *s, size_t len)\ +{ return N ## _add(B, flatcc_builder_create_string(B, s, len)); }\ +static inline int N ## _create_str(NS ## builder_t *B, const char *s)\ +{ return N ## _add(B, flatcc_builder_create_string_str(B, s)); }\ +static inline int N ## _create_strn(NS ## builder_t *B, const char *s, size_t max_len)\ +{ return N ## _add(B, flatcc_builder_create_string_strn(B, s, max_len)); }\ +static inline int N ## _clone(NS ## builder_t *B, NS ## string_t string)\ +{ return N ## _add(B, NS ## string_clone(B, string)); }\ +static inline int N ## _slice(NS ## builder_t *B, NS ## string_t string, size_t index, size_t len)\ +{ return N ## _add(B, NS ## string_slice(B, string, index, len)); }\ +__flatbuffers_build_string_ops(NS, N) + +#define __flatbuffers_build_table_vector_field(ID, NS, N, TN)\ +__flatbuffers_build_offset_vector_field(ID, NS, N, TN)\ +__flatbuffers_build_table_vector_ops(NS, N, TN) + +#define __flatbuffers_build_string_vector_field(ID, NS, N)\ +__flatbuffers_build_offset_vector_field(ID, NS, N, NS ## string)\ +__flatbuffers_build_string_vector_ops(NS, N) + +#define __flatbuffers_uint8_formal_args , uint8_t v0 +#define __flatbuffers_uint8_call_args , v0 +#define __flatbuffers_int8_formal_args , int8_t v0 +#define __flatbuffers_int8_call_args , v0 +#define __flatbuffers_bool_formal_args , flatbuffers_bool_t v0 +#define __flatbuffers_bool_call_args , v0 +#define __flatbuffers_uint16_formal_args , uint16_t v0 +#define __flatbuffers_uint16_call_args , v0 +#define __flatbuffers_uint32_formal_args , uint32_t v0 +#define __flatbuffers_uint32_call_args , v0 +#define __flatbuffers_uint64_formal_args , uint64_t v0 +#define __flatbuffers_uint64_call_args , v0 +#define __flatbuffers_int16_formal_args , int16_t v0 +#define __flatbuffers_int16_call_args , v0 +#define __flatbuffers_int32_formal_args , int32_t v0 +#define __flatbuffers_int32_call_args , v0 +#define __flatbuffers_int64_formal_args , int64_t v0 +#define __flatbuffers_int64_call_args , v0 +#define __flatbuffers_float_formal_args , float v0 +#define __flatbuffers_float_call_args , v0 +#define __flatbuffers_double_formal_args , double v0 +#define __flatbuffers_double_call_args , v0 + +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_uint8, uint8_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_int8, int8_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_bool, flatbuffers_bool_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_uint16, uint16_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_uint32, uint32_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_uint64, uint64_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_int16, int16_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_int32, int32_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_int64, int64_t) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_float, float) +__flatbuffers_build_scalar(flatbuffers_, flatbuffers_double, double) + +__flatbuffers_build_string(flatbuffers_) + +__flatbuffers_build_buffer(flatbuffers_) +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* FLATBUFFERS_COMMON_BUILDER_H */ diff --git a/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_reader.h b/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_reader.h new file mode 100644 index 0000000..fc9e3fa --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/flatbuffers_common_reader.h @@ -0,0 +1,431 @@ +#ifndef FLATBUFFERS_COMMON_READER_H +#define FLATBUFFERS_COMMON_READER_H + +/* Generated by flatcc 0.4.2 FlatBuffers schema compiler for C by dvide.com */ + +/* Common FlatBuffers read functionality for C. */ + +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" +#include "flatcc/flatcc_flatbuffers.h" + + +#define __flatbuffers_read_scalar_at_byteoffset(N, p, o) N ## _read_from_pe((uint8_t *)(p) + (o)) +#define __flatbuffers_read_scalar(N, p) N ## _read_from_pe(p) +#define __flatbuffers_read_vt(ID, offset, t)\ +flatbuffers_voffset_t offset = 0;\ +{ flatbuffers_voffset_t id, *vt;\ + assert(t != 0 && "null pointer table access");\ + id = ID;\ + vt = (flatbuffers_voffset_t *)((uint8_t *)(t) -\ + __flatbuffers_soffset_read_from_pe(t));\ + if (__flatbuffers_voffset_read_from_pe(vt) >= sizeof(vt[0]) * (id + 3)) {\ + offset = __flatbuffers_voffset_read_from_pe(vt + id + 2);\ + }\ +} +#define __flatbuffers_field_present(ID, t) { __flatbuffers_read_vt(ID, offset, t) return offset != 0; } +#define __flatbuffers_union_type_field(ID, t)\ +{\ + __flatbuffers_read_vt(ID, offset, t)\ + return offset ? __flatbuffers_read_scalar_at_byteoffset(__flatbuffers_utype, t, offset) : 0;\ +} +#define __flatbuffers_define_union_field(ID, N, NK, r)\ +static inline flatbuffers_utype_t N ## _ ## NK ## _type(N ## _table_t t)\ +__flatbuffers_union_type_field(((ID) - 1), t)\ +static inline flatbuffers_generic_table_t N ## _ ## NK(N ## _table_t t)\ +__flatbuffers_table_field(flatbuffers_generic_table_t, ID, t, r)\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t) +#define __flatbuffers_define_scalar_field(ID, N, NK, TK, T, V)\ +static inline T N ## _ ## NK (N ## _table_t t)\ +{ __flatbuffers_read_vt(ID, offset, t)\ + return offset ? __flatbuffers_read_scalar_at_byteoffset(TK, t, offset) : V;\ +}\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t)\ +__flatbuffers_define_scan_by_scalar_field(N, NK, T) +#define __flatbuffers_struct_field(T, ID, t, r)\ +{\ + __flatbuffers_read_vt(ID, offset, t)\ + if (offset) {\ + return (T)((uint8_t *)(t) + offset);\ + }\ + assert(!(r) && "required field missing");\ + return 0;\ +} +#define __flatbuffers_offset_field(T, ID, t, r, adjust)\ +{\ + flatbuffers_uoffset_t *elem;\ + __flatbuffers_read_vt(ID, offset, t)\ + if (offset) {\ + elem = (flatbuffers_uoffset_t *)((uint8_t *)(t) + offset);\ + /* Add sizeof so C api can have raw access past header field. */\ + return (T)((uint8_t *)(elem) + adjust +\ + __flatbuffers_uoffset_read_from_pe(elem));\ + }\ + assert(!(r) && "required field missing");\ + return 0;\ +} +#define __flatbuffers_vector_field(T, ID, t, r) __flatbuffers_offset_field(T, ID, t, r, sizeof(flatbuffers_uoffset_t)) +#define __flatbuffers_table_field(T, ID, t, r) __flatbuffers_offset_field(T, ID, t, r, 0) +#define __flatbuffers_define_struct_field(ID, N, NK, T, r)\ +static inline T N ## _ ## NK(N ## _table_t t)\ +__flatbuffers_struct_field(T, ID, t, r)\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t) +#define __flatbuffers_define_vector_field(ID, N, NK, T, r)\ +static inline T N ## _ ## NK(N ## _table_t t)\ +__flatbuffers_vector_field(T, ID, t, r)\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t) +#define __flatbuffers_define_table_field(ID, N, NK, T, r)\ +static inline T N ## _ ## NK(N ## _table_t t)\ +__flatbuffers_table_field(T, ID, t, r)\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t) +#define __flatbuffers_define_string_field(ID, N, NK, r)\ +static inline flatbuffers_string_t N ## _ ## NK(N ## _table_t t)\ +__flatbuffers_vector_field(flatbuffers_string_t, ID, t, r)\ +static inline int N ## _ ## NK ## _is_present(N ## _table_t t)\ +__flatbuffers_field_present(ID, t)\ +__flatbuffers_define_scan_by_string_field(N, NK) +#define __flatbuffers_vec_len(vec)\ +{ return (vec) ? (size_t)__flatbuffers_uoffset_read_from_pe((flatbuffers_uoffset_t *)vec - 1) : 0; } +#define __flatbuffers_string_len(s) __flatbuffers_vec_len(s) +static inline size_t flatbuffers_vec_len(const void *vec) +__flatbuffers_vec_len(vec) +#define __flatbuffers_scalar_vec_at(N, vec, i)\ +{ assert(flatbuffers_vec_len(vec) > (i) && "index out of range");\ + return __flatbuffers_read_scalar(N, &(vec)[i]); } +#define __flatbuffers_struct_vec_at(vec, i)\ +{ assert(flatbuffers_vec_len(vec) > (i) && "index out of range"); return (vec) + (i); } +/* `adjust` skips past the header for string vectors. */ +#define __flatbuffers_offset_vec_at(T, vec, i, adjust)\ +{ const flatbuffers_uoffset_t *elem = (vec) + (i);\ + assert(flatbuffers_vec_len(vec) > (i) && "index out of range");\ + return (T)((uint8_t *)(elem) + (size_t)__flatbuffers_uoffset_read_from_pe(elem) + adjust); } +#define __flatbuffers_define_scalar_vec_len(N) \ +static inline size_t N ## _vec_len(N ##_vec_t vec)\ +{ return flatbuffers_vec_len(vec); } +#define __flatbuffers_define_scalar_vec_at(N, T) \ +static inline T N ## _vec_at(N ## _vec_t vec, size_t i)\ +__flatbuffers_scalar_vec_at(N, vec, i) +typedef const char *flatbuffers_string_t; +static inline size_t flatbuffers_string_len(flatbuffers_string_t s) +__flatbuffers_string_len(s) +typedef const flatbuffers_uoffset_t *flatbuffers_string_vec_t; +typedef flatbuffers_uoffset_t *flatbuffers_string_mutable_vec_t; +static inline size_t flatbuffers_string_vec_len(flatbuffers_string_vec_t vec) +__flatbuffers_vec_len(vec) +static inline flatbuffers_string_t flatbuffers_string_vec_at(flatbuffers_string_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(flatbuffers_string_t, vec, i, sizeof(vec[0])) +typedef const void *flatbuffers_generic_table_t; +#include +static size_t flatbuffers_not_found = (size_t)-1; +static size_t flatbuffers_end = (size_t)-1; +#define __flatbuffers_identity(n) (n) +#define __flatbuffers_min(a, b) ((a) < (b) ? (a) : (b)) +/* Subtraction doesn't work for unsigned types. */ +#define __flatbuffers_scalar_cmp(x, y, n) ((x) < (y) ? -1 : (x) > (y)) +static inline int __flatbuffers_string_n_cmp(flatbuffers_string_t v, const char *s, size_t n) +{ size_t nv = flatbuffers_string_len(v); int x = strncmp(v, s, nv < n ? nv : n); + return x != 0 ? x : nv < n ? -1 : nv > n; } +/* `n` arg unused, but needed by string find macro expansion. */ +static inline int __flatbuffers_string_cmp(flatbuffers_string_t v, const char *s, size_t n) { (void)n; return strcmp(v, s); } +/* A = identity if searching scalar vectors rather than key fields. */ +/* Returns lowest matching index or not_found. */ +#define __flatbuffers_find_by_field(A, V, E, L, K, Kn, T, D)\ +{ T v; size_t a = 0, b, m; if (!(b = L(V))) { return flatbuffers_not_found; }\ + --b;\ + while (a < b) {\ + m = a + ((b - a) >> 1);\ + v = A(E(V, m));\ + if ((D(v, (K), (Kn))) < 0) {\ + a = m + 1;\ + } else {\ + b = m;\ + }\ + }\ + if (a == b) {\ + v = A(E(V, a));\ + if (D(v, (K), (Kn)) == 0) {\ + return a;\ + }\ + }\ + return flatbuffers_not_found;\ +} +#define __flatbuffers_find_by_scalar_field(A, V, E, L, K, T)\ +__flatbuffers_find_by_field(A, V, E, L, K, 0, T, __flatbuffers_scalar_cmp) +#define __flatbuffers_find_by_string_field(A, V, E, L, K)\ +__flatbuffers_find_by_field(A, V, E, L, K, 0, flatbuffers_string_t, __flatbuffers_string_cmp) +#define __flatbuffers_find_by_string_n_field(A, V, E, L, K, Kn)\ +__flatbuffers_find_by_field(A, V, E, L, K, Kn, flatbuffers_string_t, __flatbuffers_string_n_cmp) +#define __flatbuffers_define_find_by_scalar_field(N, NK, TK)\ +static inline size_t N ## _vec_find_by_ ## NK(N ## _vec_t vec, TK key)\ +__flatbuffers_find_by_scalar_field(N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, key, TK) +#define __flatbuffers_define_scalar_find(N, T)\ +static inline size_t N ## _vec_find(N ## _vec_t vec, T key)\ +__flatbuffers_find_by_scalar_field(__flatbuffers_identity, vec, N ## _vec_at, N ## _vec_len, key, T) +#define __flatbuffers_define_find_by_string_field(N, NK) \ +/* Note: find only works on vectors sorted by this field. */\ +static inline size_t N ## _vec_find_by_ ## NK(N ## _vec_t vec, const char *s)\ +__flatbuffers_find_by_string_field(N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s)\ +static inline size_t N ## _vec_find_n_by_ ## NK(N ## _vec_t vec, const char *s, int n)\ +__flatbuffers_find_by_string_n_field(N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s, n) +#define __flatbuffers_define_default_find_by_scalar_field(N, NK, TK)\ +static inline size_t N ## _vec_find(N ## _vec_t vec, TK key)\ +{ return N ## _vec_find_by_ ## NK(vec, key); } +#define __flatbuffers_define_default_find_by_string_field(N, NK) \ +static inline size_t N ## _vec_find(N ## _vec_t vec, const char *s)\ +{ return N ## _vec_find_by_ ## NK(vec, s); }\ +static inline size_t N ## _vec_find_n(N ## _vec_t vec, const char *s, int n)\ +{ return N ## _vec_find_n_by_ ## NK(vec, s, n); } +/* A = identity if searching scalar vectors rather than key fields. */ +/* Returns lowest matching index or not_found. */ +#define __flatbuffers_scan_by_field(b, e, A, V, E, L, K, Kn, T, D)\ +{ T v; size_t i;\ + for (i = b; i < e; ++i) {\ + v = A(E(V, i));\ + if (D(v, (K), (Kn)) == 0) {\ + return i;\ + }\ + }\ + return flatbuffers_not_found;\ +} +#define __flatbuffers_rscan_by_field(b, e, A, V, E, L, K, Kn, T, D)\ +{ T v; size_t i = e;\ + while (i-- > b) {\ + v = A(E(V, i));\ + if (D(v, (K), (Kn)) == 0) {\ + return i;\ + }\ + }\ + return flatbuffers_not_found;\ +} +#define __flatbuffers_scan_by_scalar_field(b, e, A, V, E, L, K, T)\ +__flatbuffers_scan_by_field(b, e, A, V, E, L, K, 0, T, __flatbuffers_scalar_cmp) +#define __flatbuffers_scan_by_string_field(b, e, A, V, E, L, K)\ +__flatbuffers_scan_by_field(b, e, A, V, E, L, K, 0, flatbuffers_string_t, __flatbuffers_string_cmp) +#define __flatbuffers_scan_by_string_n_field(b, e, A, V, E, L, K, Kn)\ +__flatbuffers_scan_by_field(b, e, A, V, E, L, K, Kn, flatbuffers_string_t, __flatbuffers_string_n_cmp) +#define __flatbuffers_rscan_by_scalar_field(b, e, A, V, E, L, K, T)\ +__flatbuffers_rscan_by_field(b, e, A, V, E, L, K, 0, T, __flatbuffers_scalar_cmp) +#define __flatbuffers_rscan_by_string_field(b, e, A, V, E, L, K)\ +__flatbuffers_rscan_by_field(b, e, A, V, E, L, K, 0, flatbuffers_string_t, __flatbuffers_string_cmp) +#define __flatbuffers_rscan_by_string_n_field(b, e, A, V, E, L, K, Kn)\ +__flatbuffers_rscan_by_field(b, e, A, V, E, L, K, Kn, flatbuffers_string_t, __flatbuffers_string_n_cmp) +#define __flatbuffers_define_scan_by_scalar_field(N, NK, T)\ +static inline size_t N ## _vec_scan_by_ ## NK(N ## _vec_t vec, T key)\ +__flatbuffers_scan_by_scalar_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_scan_ex_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, T key)\ +__flatbuffers_scan_by_scalar_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_rscan_by_ ## NK(N ## _vec_t vec, T key)\ +__flatbuffers_rscan_by_scalar_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_rscan_ex_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, T key)\ +__flatbuffers_rscan_by_scalar_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, key, T) +#define __flatbuffers_define_scalar_scan(N, T)\ +static inline size_t N ## _vec_scan(N ## _vec_t vec, T key)\ +__flatbuffers_scan_by_scalar_field(0, N ## _vec_len(vec), __flatbuffers_identity, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_scan_ex(N ## _vec_t vec, size_t begin, size_t end, T key)\ +__flatbuffers_scan_by_scalar_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), __flatbuffers_identity, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_rscan(N ## _vec_t vec, T key)\ +__flatbuffers_rscan_by_scalar_field(0, N ## _vec_len(vec), __flatbuffers_identity, vec, N ## _vec_at, N ## _vec_len, key, T)\ +static inline size_t N ## _vec_rscan_ex(N ## _vec_t vec, size_t begin, size_t end, T key)\ +__flatbuffers_rscan_by_scalar_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), __flatbuffers_identity, vec, N ## _vec_at, N ## _vec_len, key, T) +#define __flatbuffers_define_scan_by_string_field(N, NK) \ +static inline size_t N ## _vec_scan_by_ ## NK(N ## _vec_t vec, const char *s)\ +__flatbuffers_scan_by_string_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s)\ +static inline size_t N ## _vec_scan_n_by_ ## NK(N ## _vec_t vec, const char *s, int n)\ +__flatbuffers_scan_by_string_n_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s, n)\ +static inline size_t N ## _vec_scan_ex_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, const char *s)\ +__flatbuffers_scan_by_string_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s)\ +static inline size_t N ## _vec_scan_ex_n_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, const char *s, int n)\ +__flatbuffers_scan_by_string_n_field(begin, __flatbuffers_min( end, N ## _vec_len(vec) ), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s, n)\ +static inline size_t N ## _vec_rscan_by_ ## NK(N ## _vec_t vec, const char *s)\ +__flatbuffers_rscan_by_string_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s)\ +static inline size_t N ## _vec_rscan_n_by_ ## NK(N ## _vec_t vec, const char *s, int n)\ +__flatbuffers_rscan_by_string_n_field(0, N ## _vec_len(vec), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s, n)\ +static inline size_t N ## _vec_rscan_ex_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, const char *s)\ +__flatbuffers_rscan_by_string_field(begin, __flatbuffers_min(end, N ## _vec_len(vec)), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s)\ +static inline size_t N ## _vec_rscan_ex_n_by_ ## NK(N ## _vec_t vec, size_t begin, size_t end, const char *s, int n)\ +__flatbuffers_rscan_by_string_n_field(begin, __flatbuffers_min( end, N ## _vec_len(vec) ), N ## _ ## NK, vec, N ## _vec_at, N ## _vec_len, s, n) +#define __flatbuffers_define_default_scan_by_scalar_field(N, NK, TK)\ +static inline size_t N ## _vec_scan(N ## _vec_t vec, TK key)\ +{ return N ## _vec_scan_by_ ## NK(vec, key); }\ +static inline size_t N ## _vec_scan_ex(N ## _vec_t vec, size_t begin, size_t end, TK key)\ +{ return N ## _vec_scan_ex_by_ ## NK(vec, begin, end, key); }\ +static inline size_t N ## _vec_rscan(N ## _vec_t vec, TK key)\ +{ return N ## _vec_rscan_by_ ## NK(vec, key); }\ +static inline size_t N ## _vec_rscan_ex(N ## _vec_t vec, size_t begin, size_t end, TK key)\ +{ return N ## _vec_rscan_ex_by_ ## NK(vec, begin, end, key); } +#define __flatbuffers_define_default_scan_by_string_field(N, NK) \ +static inline size_t N ## _vec_scan(N ## _vec_t vec, const char *s)\ +{ return N ## _vec_scan_by_ ## NK(vec, s); }\ +static inline size_t N ## _vec_scan_n(N ## _vec_t vec, const char *s, int n)\ +{ return N ## _vec_scan_n_by_ ## NK(vec, s, n); }\ +static inline size_t N ## _vec_scan_ex(N ## _vec_t vec, size_t begin, size_t end, const char *s)\ +{ return N ## _vec_scan_ex_by_ ## NK(vec, begin, end, s); }\ +static inline size_t N ## _vec_scan_ex_n(N ## _vec_t vec, size_t begin, size_t end, const char *s, int n)\ +{ return N ## _vec_scan_ex_n_by_ ## NK(vec, begin, end, s, n); }\ +static inline size_t N ## _vec_rscan(N ## _vec_t vec, const char *s)\ +{ return N ## _vec_rscan_by_ ## NK(vec, s); }\ +static inline size_t N ## _vec_rscan_n(N ## _vec_t vec, const char *s, int n)\ +{ return N ## _vec_rscan_n_by_ ## NK(vec, s, n); }\ +static inline size_t N ## _vec_rscan_ex(N ## _vec_t vec, size_t begin, size_t end, const char *s)\ +{ return N ## _vec_rscan_ex_by_ ## NK(vec, begin, end, s); }\ +static inline size_t N ## _vec_rscan_ex_n(N ## _vec_t vec, size_t begin, size_t end, const char *s, int n)\ +{ return N ## _vec_rscan_ex_n_by_ ## NK(vec, begin, end, s, n); } +#define __flatbuffers_heap_sort(N, X, A, E, L, TK, TE, D, S)\ +static inline void __ ## N ## X ## __heap_sift_down(\ + N ## _mutable_vec_t vec, size_t start, size_t end)\ +{ size_t child, root; TK v1, v2, vroot;\ + root = start;\ + while ((root << 1) <= end) {\ + child = root << 1;\ + if (child < end) {\ + v1 = A(E(vec, child));\ + v2 = A(E(vec, child + 1));\ + if (D(v1, v2) < 0) {\ + child++;\ + }\ + }\ + vroot = A(E(vec, root));\ + v1 = A(E(vec, child));\ + if (D(vroot, v1) < 0) {\ + S(vec, root, child, TE);\ + root = child;\ + } else {\ + return;\ + }\ + }\ +}\ +static inline void __ ## N ## X ## __heap_sort(N ## _mutable_vec_t vec)\ +{ size_t start, end, size;\ + size = L(vec); if (size == 0) return; end = size - 1; start = size >> 1;\ + do { __ ## N ## X ## __heap_sift_down(vec, start, end); } while (start--);\ + while (end > 0) { \ + S(vec, 0, end, TE);\ + __ ## N ## X ## __heap_sift_down(vec, 0, --end); } } +#define __flatbuffers_define_sort_by_field(N, NK, TK, TE, D, S)\ + __flatbuffers_heap_sort(N, _sort_by_ ## NK, N ## _ ## NK, N ## _vec_at, N ## _vec_len, TK, TE, D, S)\ +static inline void N ## _vec_sort_by_ ## NK(N ## _mutable_vec_t vec)\ +{ __ ## N ## _sort_by_ ## NK ## __heap_sort(vec); } +#define __flatbuffers_define_sort(N, TK, TE, D, S)\ +__flatbuffers_heap_sort(N, , __flatbuffers_identity, N ## _vec_at, N ## _vec_len, TK, TE, D, S)\ +static inline void N ## _vec_sort(N ## _mutable_vec_t vec) { __ ## N ## __heap_sort(vec); } +#define __flatbuffers_scalar_diff(x, y) ((x) < (y) ? -1 : (x) > (y)) +#define __flatbuffers_string_diff(x, y) __flatbuffers_string_n_cmp((x), (const char *)(y), flatbuffers_string_len(y)) +#define __flatbuffers_scalar_swap(vec, a, b, TE) { TE tmp = vec[b]; vec[b] = vec[a]; vec[a] = tmp; } +#define __flatbuffers_string_swap(vec, a, b, TE)\ +{ TE ta, tb, d;\ + d = (TE)((a - b) * sizeof(vec[0]));\ + ta = __flatbuffers_uoffset_read_from_pe(vec + b) - d;\ + tb = __flatbuffers_uoffset_read_from_pe(vec + a) + d;\ + __flatbuffers_uoffset_write_to_pe(vec + a, ta);\ + __flatbuffers_uoffset_write_to_pe(vec + b, tb); } +#define __flatbuffers_define_sort_by_scalar_field(N, NK, TK, TE)\ + __flatbuffers_define_sort_by_field(N, NK, TK, TE, __flatbuffers_scalar_diff, __flatbuffers_scalar_swap) +#define __flatbuffers_define_sort_by_string_field(N, NK)\ + __flatbuffers_define_sort_by_field(N, NK, flatbuffers_string_t, flatbuffers_uoffset_t, __flatbuffers_string_diff, __flatbuffers_string_swap) +#define __flatbuffers_define_scalar_sort(N, T) __flatbuffers_define_sort(N, T, T, __flatbuffers_scalar_diff, __flatbuffers_scalar_swap) +#define __flatbuffers_define_string_sort() __flatbuffers_define_sort(flatbuffers_string, flatbuffers_string_t, flatbuffers_uoffset_t, __flatbuffers_string_diff, __flatbuffers_string_swap) +#define __flatbuffers_define_scalar_vector(N, T)\ +typedef const T *N ## _vec_t;\ +typedef T *N ## _mutable_vec_t;\ +__flatbuffers_define_scalar_vec_len(N)\ +__flatbuffers_define_scalar_vec_at(N, T)\ +__flatbuffers_define_scalar_find(N, T)\ +__flatbuffers_define_scalar_scan(N, T)\ +__flatbuffers_define_scalar_sort(N, T) + +#define __flatbuffers_define_integer_type(N, T, W)\ +__flatcc_define_integer_accessors(N, T, W, flatbuffers_endian)\ +__flatbuffers_define_scalar_vector(N, T) +__flatbuffers_define_scalar_vector(flatbuffers_bool, flatbuffers_bool_t) +__flatbuffers_define_scalar_vector(flatbuffers_uint8, uint8_t) +__flatbuffers_define_scalar_vector(flatbuffers_int8, int8_t) +__flatbuffers_define_scalar_vector(flatbuffers_uint16, uint16_t) +__flatbuffers_define_scalar_vector(flatbuffers_int16, int16_t) +__flatbuffers_define_scalar_vector(flatbuffers_uint32, uint32_t) +__flatbuffers_define_scalar_vector(flatbuffers_int32, int32_t) +__flatbuffers_define_scalar_vector(flatbuffers_uint64, uint64_t) +__flatbuffers_define_scalar_vector(flatbuffers_int64, int64_t) +__flatbuffers_define_scalar_vector(flatbuffers_float, float) +__flatbuffers_define_scalar_vector(flatbuffers_double, double) +static inline size_t flatbuffers_string_vec_find(flatbuffers_string_vec_t vec, const char *s) +__flatbuffers_find_by_string_field(__flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s) +static inline size_t flatbuffers_string_vec_find_n(flatbuffers_string_vec_t vec, const char *s, size_t n) +__flatbuffers_find_by_string_n_field(__flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s, n) +static inline size_t flatbuffers_string_vec_scan(flatbuffers_string_vec_t vec, const char *s) +__flatbuffers_scan_by_string_field(0, flatbuffers_string_vec_len(vec), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s) +static inline size_t flatbuffers_string_vec_scan_n(flatbuffers_string_vec_t vec, const char *s, size_t n) +__flatbuffers_scan_by_string_n_field(0, flatbuffers_string_vec_len(vec), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s, n) +static inline size_t flatbuffers_string_vec_scan_ex(flatbuffers_string_vec_t vec, size_t begin, size_t end, const char *s) +__flatbuffers_scan_by_string_field(begin, __flatbuffers_min(end, flatbuffers_string_vec_len(vec)), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s) +static inline size_t flatbuffers_string_vec_scan_ex_n(flatbuffers_string_vec_t vec, size_t begin, size_t end, const char *s, size_t n) +__flatbuffers_scan_by_string_n_field(begin, __flatbuffers_min(end, flatbuffers_string_vec_len(vec)), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s, n) +static inline size_t flatbuffers_string_vec_rscan(flatbuffers_string_vec_t vec, const char *s) +__flatbuffers_rscan_by_string_field(0, flatbuffers_string_vec_len(vec), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s) +static inline size_t flatbuffers_string_vec_rscan_n(flatbuffers_string_vec_t vec, const char *s, size_t n) +__flatbuffers_rscan_by_string_n_field(0, flatbuffers_string_vec_len(vec), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s, n) +static inline size_t flatbuffers_string_vec_rscan_ex(flatbuffers_string_vec_t vec, size_t begin, size_t end, const char *s) +__flatbuffers_rscan_by_string_field(begin, __flatbuffers_min(end, flatbuffers_string_vec_len(vec)), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s) +static inline size_t flatbuffers_string_vec_rscan_ex_n(flatbuffers_string_vec_t vec, size_t begin, size_t end, const char *s, size_t n) +__flatbuffers_rscan_by_string_n_field(begin, __flatbuffers_min(end, flatbuffers_string_vec_len(vec)), __flatbuffers_identity, vec, flatbuffers_string_vec_at, flatbuffers_string_vec_len, s, n) +__flatbuffers_define_string_sort() +#define __flatbuffers_define_struct_scalar_field(N, NK, TK, T)\ +static inline T N ## _ ## NK (N ## _struct_t t)\ +{ return t ? __flatbuffers_read_scalar(TK, &(t->NK)) : 0; }\ +__flatbuffers_define_scan_by_scalar_field(N, NK, T) +#define __flatbuffers_define_struct_struct_field(N, NK, T)\ +static inline T N ## _ ## NK(N ## _struct_t t) { return t ? &(t->NK) : 0; } +/* If fid is null, the function returns true without testing as buffer is not expected to have any id. */ +static inline int flatbuffers_has_identifier(const void *buffer, const char *fid) +{ flatbuffers_thash_t id, id2 = 0; if (fid == 0) { return 1; }; + strncpy((char *)&id2, fid, sizeof(id2)); + /* Identifier strings are always considered little endian. */ + id2 = __flatbuffers_thash_cast_from_le(id2); + id = __flatbuffers_thash_read_from_pe(((flatbuffers_uoffset_t *)buffer) + 1); + return id2 == 0 || id == id2; } +static inline int flatbuffers_has_type_hash(const void *buffer, flatbuffers_thash_t thash) +{ return thash == 0 || (__flatbuffers_thash_read_from_pe((flatbuffers_uoffset_t *)buffer + 1) == thash); } + +static inline flatbuffers_thash_t flatbuffers_get_type_hash(const void *buffer) +{ return __flatbuffers_thash_read_from_pe((flatbuffers_uoffset_t *)buffer + 1); } + +#define flatbuffers_verify_endian() flatbuffers_has_identifier("\x00\x00\x00\x00" "1234", "1234") +static inline void *flatbuffers_read_size_prefix(void *b, size_t *size_out) +{ if (size_out) { *size_out = (size_t)__flatbuffers_uoffset_read_from_pe(b); } + return (uint8_t *)b + sizeof(flatbuffers_uoffset_t); } +/* Null file identifier accepts anything, otherwise fid should be 4 characters. */ +#define __flatbuffers_read_root(T, K, buffer, fid)\ + ((!buffer || !flatbuffers_has_identifier(buffer, fid)) ? 0 :\ + ((T ## _ ## K ## t)(((uint8_t *)buffer) +\ + __flatbuffers_uoffset_read_from_pe(buffer)))) +#define __flatbuffers_read_typed_root(T, K, buffer, thash)\ + ((!buffer || !flatbuffers_has_type_hash(buffer, thash)) ? 0 :\ + ((T ## _ ## K ## t)(((uint8_t *)buffer) +\ + __flatbuffers_uoffset_read_from_pe(buffer)))) +#define __flatbuffers_nested_buffer_as_root(C, N, T, K)\ +static inline T ## _ ## K ## t C ## _ ## N ## _as_root_with_identifier(C ## _ ## table_t t, const char *fid)\ +{ const uint8_t *buffer = C ## _ ## N(t); return __flatbuffers_read_root(T, K, buffer, fid); }\ +static inline T ## _ ## K ## t C ## _ ## N ## _as_typed_root(C ## _ ## table_t t)\ +{ const uint8_t *buffer = C ## _ ## N(t); return __flatbuffers_read_root(T, K, buffer, C ## _ ## type_identifier); }\ +static inline T ## _ ## K ## t C ## _ ## N ## _as_root(C ## _ ## table_t t)\ +{ const char *fid = T ## _identifier;\ + const uint8_t *buffer = C ## _ ## N(t); return __flatbuffers_read_root(T, K, buffer, fid); } +#define __flatbuffers_buffer_as_root(N, K)\ +static inline N ## _ ## K ## t N ## _as_root_with_identifier(const void *buffer, const char *fid)\ +{ return __flatbuffers_read_root(N, K, buffer, fid); }\ +static inline N ## _ ## K ## t N ## _as_root_with_type_hash(const void *buffer, flatbuffers_thash_t thash)\ +{ return __flatbuffers_read_typed_root(N, K, buffer, thash); }\ +static inline N ## _ ## K ## t N ## _as_root(const void *buffer)\ +{ const char *fid = N ## _identifier;\ + return __flatbuffers_read_root(N, K, buffer, fid); }\ +static inline N ## _ ## K ## t N ## _as_typed_root(const void *buffer)\ +{ return __flatbuffers_read_typed_root(N, K, buffer, N ## _type_hash); } +#define __flatbuffers_struct_as_root(N) __flatbuffers_buffer_as_root(N, struct_) +#define __flatbuffers_table_as_root(N) __flatbuffers_buffer_as_root(N, table_) + +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* FLATBUFFERS_COMMON_H */ diff --git a/Source/library/FBSUtil/flatcc/reflection/reflection_builder.h b/Source/library/FBSUtil/flatcc/reflection/reflection_builder.h new file mode 100644 index 0000000..52d7cfe --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/reflection_builder.h @@ -0,0 +1,185 @@ +#ifndef REFLECTION_BUILDER_H +#define REFLECTION_BUILDER_H + +/* Generated by flatcc 0.4.2 FlatBuffers schema compiler for C by dvide.com */ + +#ifndef REFLECTION_READER_H +#include "reflection_reader.h" +#endif +#ifndef FLATBUFFERS_COMMON_BUILDER_H +#include "flatbuffers_common_builder.h" +#endif +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" +#undef flatbuffers_identifier +#define flatbuffers_identifier "BFBS" +#undef flatbuffers_extension +#define flatbuffers_extension ".bfbs" + +#define __reflection_BaseType_formal_args , reflection_BaseType_enum_t v0 +#define __reflection_BaseType_call_args , v0 +__flatbuffers_build_scalar(flatbuffers_, reflection_BaseType, reflection_BaseType_enum_t) + +static const flatbuffers_voffset_t __reflection_Type_required[] = { 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_Type, 3) +static const flatbuffers_voffset_t __reflection_EnumVal_required[] = { 0, 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_EnumVal, 3) +static const flatbuffers_voffset_t __reflection_Enum_required[] = { 0, 1, 3, 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_Enum, 4) +static const flatbuffers_voffset_t __reflection_Field_required[] = { 0, 1, 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_Field, 9) +static const flatbuffers_voffset_t __reflection_Object_required[] = { 0, 1, 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_Object, 5) +static const flatbuffers_voffset_t __reflection_Schema_required[] = { 0, 1, 0 }; +__flatbuffers_build_table(flatbuffers_, reflection_Schema, 5) +#define __reflection_Type_formal_args , reflection_BaseType_enum_t v0, reflection_BaseType_enum_t v1, int32_t v2 +#define __reflection_Type_call_args , v0, v1, v2 +static inline reflection_Type_ref_t reflection_Type_create(flatbuffers_builder_t *B __reflection_Type_formal_args); +#define __reflection_EnumVal_formal_args , flatbuffers_string_ref_t v0, int64_t v1, reflection_Object_ref_t v2 +#define __reflection_EnumVal_call_args , v0, v1, v2 +static inline reflection_EnumVal_ref_t reflection_EnumVal_create(flatbuffers_builder_t *B __reflection_EnumVal_formal_args); +#define __reflection_Enum_formal_args , flatbuffers_string_ref_t v0, reflection_EnumVal_vec_ref_t v1, flatbuffers_bool_t v2, reflection_Type_ref_t v3 +#define __reflection_Enum_call_args , v0, v1, v2, v3 +static inline reflection_Enum_ref_t reflection_Enum_create(flatbuffers_builder_t *B __reflection_Enum_formal_args); +#define __reflection_Field_formal_args ,\ + flatbuffers_string_ref_t v0, reflection_Type_ref_t v1, uint16_t v2, uint16_t v3,\ + int64_t v4, double v5, flatbuffers_bool_t v6, flatbuffers_bool_t v7, flatbuffers_bool_t v8 +#define __reflection_Field_call_args ,\ + v0, v1, v2, v3,\ + v4, v5, v6, v7, v8 +static inline reflection_Field_ref_t reflection_Field_create(flatbuffers_builder_t *B __reflection_Field_formal_args); +#define __reflection_Object_formal_args ,\ + flatbuffers_string_ref_t v0, reflection_Field_vec_ref_t v1, flatbuffers_bool_t v2, int32_t v3, int32_t v4 +#define __reflection_Object_call_args ,\ + v0, v1, v2, v3, v4 +static inline reflection_Object_ref_t reflection_Object_create(flatbuffers_builder_t *B __reflection_Object_formal_args); +#define __reflection_Schema_formal_args ,\ + reflection_Object_vec_ref_t v0, reflection_Enum_vec_ref_t v1, flatbuffers_string_ref_t v2, flatbuffers_string_ref_t v3, reflection_Object_ref_t v4 +#define __reflection_Schema_call_args ,\ + v0, v1, v2, v3, v4 +static inline reflection_Schema_ref_t reflection_Schema_create(flatbuffers_builder_t *B __reflection_Schema_formal_args); + +__flatbuffers_build_scalar_field(0, flatbuffers_, reflection_Type_base_type, reflection_BaseType, reflection_BaseType_enum_t, 1, 1, INT8_C(0)) +__flatbuffers_build_scalar_field(1, flatbuffers_, reflection_Type_element, reflection_BaseType, reflection_BaseType_enum_t, 1, 1, INT8_C(0)) +__flatbuffers_build_scalar_field(2, flatbuffers_, reflection_Type_index, flatbuffers_int32, int32_t, 4, 4, INT32_C(-1)) + +static inline reflection_Type_ref_t reflection_Type_create(flatbuffers_builder_t *B __reflection_Type_formal_args) +{ + if (reflection_Type_start(B) + || reflection_Type_index_add(B, v2) + || reflection_Type_base_type_add(B, v0) + || reflection_Type_element_add(B, v1)) { + return 0; + } + return reflection_Type_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_Type, reflection_Type_identifier, reflection_Type_type_identifier) + +__flatbuffers_build_string_field(0, flatbuffers_, reflection_EnumVal_name) +__flatbuffers_build_scalar_field(1, flatbuffers_, reflection_EnumVal_value, flatbuffers_int64, int64_t, 8, 8, INT64_C(0)) +__flatbuffers_build_table_field(2, flatbuffers_, reflection_EnumVal_object, reflection_Object) + +static inline reflection_EnumVal_ref_t reflection_EnumVal_create(flatbuffers_builder_t *B __reflection_EnumVal_formal_args) +{ + if (reflection_EnumVal_start(B) + || reflection_EnumVal_value_add(B, v1) + || reflection_EnumVal_name_add(B, v0) + || reflection_EnumVal_object_add(B, v2)) { + return 0; + } + return reflection_EnumVal_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_EnumVal, reflection_EnumVal_identifier, reflection_EnumVal_type_identifier) + +__flatbuffers_build_string_field(0, flatbuffers_, reflection_Enum_name) +/* vector has keyed elements */ +__flatbuffers_build_table_vector_field(1, flatbuffers_, reflection_Enum_values, reflection_EnumVal) +__flatbuffers_build_scalar_field(2, flatbuffers_, reflection_Enum_is_union, flatbuffers_bool, flatbuffers_bool_t, 1, 1, UINT8_C(0)) +__flatbuffers_build_table_field(3, flatbuffers_, reflection_Enum_underlying_type, reflection_Type) + +static inline reflection_Enum_ref_t reflection_Enum_create(flatbuffers_builder_t *B __reflection_Enum_formal_args) +{ + if (reflection_Enum_start(B) + || reflection_Enum_name_add(B, v0) + || reflection_Enum_values_add(B, v1) + || reflection_Enum_underlying_type_add(B, v3) + || reflection_Enum_is_union_add(B, v2)) { + return 0; + } + return reflection_Enum_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_Enum, reflection_Enum_identifier, reflection_Enum_type_identifier) + +__flatbuffers_build_string_field(0, flatbuffers_, reflection_Field_name) +__flatbuffers_build_table_field(1, flatbuffers_, reflection_Field_type, reflection_Type) +__flatbuffers_build_scalar_field(2, flatbuffers_, reflection_Field_id, flatbuffers_uint16, uint16_t, 2, 2, UINT16_C(0)) +__flatbuffers_build_scalar_field(3, flatbuffers_, reflection_Field_offset, flatbuffers_uint16, uint16_t, 2, 2, UINT16_C(0)) +__flatbuffers_build_scalar_field(4, flatbuffers_, reflection_Field_default_integer, flatbuffers_int64, int64_t, 8, 8, INT64_C(0)) +__flatbuffers_build_scalar_field(5, flatbuffers_, reflection_Field_default_real, flatbuffers_double, double, 8, 8, 0.000000) +__flatbuffers_build_scalar_field(6, flatbuffers_, reflection_Field_deprecated, flatbuffers_bool, flatbuffers_bool_t, 1, 1, UINT8_C(0)) +__flatbuffers_build_scalar_field(7, flatbuffers_, reflection_Field_required, flatbuffers_bool, flatbuffers_bool_t, 1, 1, UINT8_C(0)) +__flatbuffers_build_scalar_field(8, flatbuffers_, reflection_Field_key, flatbuffers_bool, flatbuffers_bool_t, 1, 1, UINT8_C(0)) + +static inline reflection_Field_ref_t reflection_Field_create(flatbuffers_builder_t *B __reflection_Field_formal_args) +{ + if (reflection_Field_start(B) + || reflection_Field_default_integer_add(B, v4) + || reflection_Field_default_real_add(B, v5) + || reflection_Field_name_add(B, v0) + || reflection_Field_type_add(B, v1) + || reflection_Field_id_add(B, v2) + || reflection_Field_offset_add(B, v3) + || reflection_Field_deprecated_add(B, v6) + || reflection_Field_required_add(B, v7) + || reflection_Field_key_add(B, v8)) { + return 0; + } + return reflection_Field_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_Field, reflection_Field_identifier, reflection_Field_type_identifier) + +__flatbuffers_build_string_field(0, flatbuffers_, reflection_Object_name) +/* vector has keyed elements */ +__flatbuffers_build_table_vector_field(1, flatbuffers_, reflection_Object_fields, reflection_Field) +__flatbuffers_build_scalar_field(2, flatbuffers_, reflection_Object_is_struct, flatbuffers_bool, flatbuffers_bool_t, 1, 1, UINT8_C(0)) +__flatbuffers_build_scalar_field(3, flatbuffers_, reflection_Object_minalign, flatbuffers_int32, int32_t, 4, 4, INT32_C(0)) +__flatbuffers_build_scalar_field(4, flatbuffers_, reflection_Object_bytesize, flatbuffers_int32, int32_t, 4, 4, INT32_C(0)) + +static inline reflection_Object_ref_t reflection_Object_create(flatbuffers_builder_t *B __reflection_Object_formal_args) +{ + if (reflection_Object_start(B) + || reflection_Object_name_add(B, v0) + || reflection_Object_fields_add(B, v1) + || reflection_Object_minalign_add(B, v3) + || reflection_Object_bytesize_add(B, v4) + || reflection_Object_is_struct_add(B, v2)) { + return 0; + } + return reflection_Object_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_Object, reflection_Object_identifier, reflection_Object_type_identifier) + +/* vector has keyed elements */ +__flatbuffers_build_table_vector_field(0, flatbuffers_, reflection_Schema_objects, reflection_Object) +/* vector has keyed elements */ +__flatbuffers_build_table_vector_field(1, flatbuffers_, reflection_Schema_enums, reflection_Enum) +__flatbuffers_build_string_field(2, flatbuffers_, reflection_Schema_file_ident) +__flatbuffers_build_string_field(3, flatbuffers_, reflection_Schema_file_ext) +__flatbuffers_build_table_field(4, flatbuffers_, reflection_Schema_root_table, reflection_Object) + +static inline reflection_Schema_ref_t reflection_Schema_create(flatbuffers_builder_t *B __reflection_Schema_formal_args) +{ + if (reflection_Schema_start(B) + || reflection_Schema_objects_add(B, v0) + || reflection_Schema_enums_add(B, v1) + || reflection_Schema_file_ident_add(B, v2) + || reflection_Schema_file_ext_add(B, v3) + || reflection_Schema_root_table_add(B, v4)) { + return 0; + } + return reflection_Schema_end(B); +} +__flatbuffers_build_table_prolog(flatbuffers_, reflection_Schema, reflection_Schema_identifier, reflection_Schema_type_identifier) + +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* REFLECTION_BUILDER_H */ diff --git a/Source/library/FBSUtil/flatcc/reflection/reflection_reader.h b/Source/library/FBSUtil/flatcc/reflection/reflection_reader.h new file mode 100644 index 0000000..a38e5e9 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/reflection_reader.h @@ -0,0 +1,221 @@ +#ifndef REFLECTION_READER_H +#define REFLECTION_READER_H + +/* Generated by flatcc 0.4.2 FlatBuffers schema compiler for C by dvide.com */ + +#ifndef FLATBUFFERS_COMMON_READER_H +#include "flatbuffers_common_reader.h" +#endif +#include "flatcc/flatcc_flatbuffers.h" +#ifndef __alignas_is_defined +#include +#endif +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" +#undef flatbuffers_identifier +#define flatbuffers_identifier "BFBS" +#undef flatbuffers_extension +#define flatbuffers_extension ".bfbs" + + +typedef const struct reflection_Type_table *reflection_Type_table_t; +typedef const flatbuffers_uoffset_t *reflection_Type_vec_t; +typedef flatbuffers_uoffset_t *reflection_Type_mutable_vec_t; +typedef const struct reflection_EnumVal_table *reflection_EnumVal_table_t; +typedef const flatbuffers_uoffset_t *reflection_EnumVal_vec_t; +typedef flatbuffers_uoffset_t *reflection_EnumVal_mutable_vec_t; +typedef const struct reflection_Enum_table *reflection_Enum_table_t; +typedef const flatbuffers_uoffset_t *reflection_Enum_vec_t; +typedef flatbuffers_uoffset_t *reflection_Enum_mutable_vec_t; +typedef const struct reflection_Field_table *reflection_Field_table_t; +typedef const flatbuffers_uoffset_t *reflection_Field_vec_t; +typedef flatbuffers_uoffset_t *reflection_Field_mutable_vec_t; +typedef const struct reflection_Object_table *reflection_Object_table_t; +typedef const flatbuffers_uoffset_t *reflection_Object_vec_t; +typedef flatbuffers_uoffset_t *reflection_Object_mutable_vec_t; +typedef const struct reflection_Schema_table *reflection_Schema_table_t; +typedef const flatbuffers_uoffset_t *reflection_Schema_vec_t; +typedef flatbuffers_uoffset_t *reflection_Schema_mutable_vec_t; + +typedef int8_t reflection_BaseType_enum_t; +__flatbuffers_define_integer_type(reflection_BaseType, reflection_BaseType_enum_t, 8) +#define reflection_BaseType_None ((reflection_BaseType_enum_t)INT8_C(0)) +#define reflection_BaseType_UType ((reflection_BaseType_enum_t)INT8_C(1)) +#define reflection_BaseType_Bool ((reflection_BaseType_enum_t)INT8_C(2)) +#define reflection_BaseType_Byte ((reflection_BaseType_enum_t)INT8_C(3)) +#define reflection_BaseType_UByte ((reflection_BaseType_enum_t)INT8_C(4)) +#define reflection_BaseType_Short ((reflection_BaseType_enum_t)INT8_C(5)) +#define reflection_BaseType_UShort ((reflection_BaseType_enum_t)INT8_C(6)) +#define reflection_BaseType_Int ((reflection_BaseType_enum_t)INT8_C(7)) +#define reflection_BaseType_UInt ((reflection_BaseType_enum_t)INT8_C(8)) +#define reflection_BaseType_Long ((reflection_BaseType_enum_t)INT8_C(9)) +#define reflection_BaseType_ULong ((reflection_BaseType_enum_t)INT8_C(10)) +#define reflection_BaseType_Float ((reflection_BaseType_enum_t)INT8_C(11)) +#define reflection_BaseType_Double ((reflection_BaseType_enum_t)INT8_C(12)) +#define reflection_BaseType_String ((reflection_BaseType_enum_t)INT8_C(13)) +#define reflection_BaseType_Vector ((reflection_BaseType_enum_t)INT8_C(14)) +#define reflection_BaseType_Obj ((reflection_BaseType_enum_t)INT8_C(15)) +#define reflection_BaseType_Union ((reflection_BaseType_enum_t)INT8_C(16)) + +static inline const char *reflection_BaseType_name(reflection_BaseType_enum_t value) +{ + switch (value) { + case reflection_BaseType_None: return "None"; + case reflection_BaseType_UType: return "UType"; + case reflection_BaseType_Bool: return "Bool"; + case reflection_BaseType_Byte: return "Byte"; + case reflection_BaseType_UByte: return "UByte"; + case reflection_BaseType_Short: return "Short"; + case reflection_BaseType_UShort: return "UShort"; + case reflection_BaseType_Int: return "Int"; + case reflection_BaseType_UInt: return "UInt"; + case reflection_BaseType_Long: return "Long"; + case reflection_BaseType_ULong: return "ULong"; + case reflection_BaseType_Float: return "Float"; + case reflection_BaseType_Double: return "Double"; + case reflection_BaseType_String: return "String"; + case reflection_BaseType_Vector: return "Vector"; + case reflection_BaseType_Obj: return "Obj"; + case reflection_BaseType_Union: return "Union"; + default: return ""; + } +} + + + +struct reflection_Type_table { uint8_t unused__; }; + +#ifndef reflection_Type_identifier +#define reflection_Type_identifier flatbuffers_identifier +#endif +#define reflection_Type_type_hash ((flatbuffers_thash_t)0x44c8fe5e) +#define reflection_Type_type_identifier "\x5e\xfe\xc8\x44" +static inline size_t reflection_Type_vec_len(reflection_Type_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_Type_table_t reflection_Type_vec_at(reflection_Type_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_Type_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_Type) + +__flatbuffers_define_scalar_field(0, reflection_Type, base_type, reflection_BaseType, reflection_BaseType_enum_t, INT8_C(0)) +__flatbuffers_define_scalar_field(1, reflection_Type, element, reflection_BaseType, reflection_BaseType_enum_t, INT8_C(0)) +__flatbuffers_define_scalar_field(2, reflection_Type, index, flatbuffers_int32, int32_t, INT32_C(-1)) + +struct reflection_EnumVal_table { uint8_t unused__; }; + +#ifndef reflection_EnumVal_identifier +#define reflection_EnumVal_identifier flatbuffers_identifier +#endif +#define reflection_EnumVal_type_hash ((flatbuffers_thash_t)0x9531c946) +#define reflection_EnumVal_type_identifier "\x46\xc9\x31\x95" +static inline size_t reflection_EnumVal_vec_len(reflection_EnumVal_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_EnumVal_table_t reflection_EnumVal_vec_at(reflection_EnumVal_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_EnumVal_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_EnumVal) + +__flatbuffers_define_string_field(0, reflection_EnumVal, name, 1) +__flatbuffers_define_scalar_field(1, reflection_EnumVal, value, flatbuffers_int64, int64_t, INT64_C(0)) +/* Note: find only works on vectors sorted by this field. */ +__flatbuffers_define_find_by_scalar_field(reflection_EnumVal, value, int64_t) +__flatbuffers_define_sort_by_scalar_field(reflection_EnumVal, value, int64_t, flatbuffers_uoffset_t) +__flatbuffers_define_default_find_by_scalar_field(reflection_EnumVal, value, int64_t) +__flatbuffers_define_default_scan_by_scalar_field(reflection_EnumVal, value, int64_t) +#define reflection_EnumVal_vec_sort reflection_EnumVal_vec_sort_by_value +__flatbuffers_define_table_field(2, reflection_EnumVal, object, reflection_Object_table_t, 0) + +struct reflection_Enum_table { uint8_t unused__; }; + +#ifndef reflection_Enum_identifier +#define reflection_Enum_identifier flatbuffers_identifier +#endif +#define reflection_Enum_type_hash ((flatbuffers_thash_t)0xacffa90f) +#define reflection_Enum_type_identifier "\x0f\xa9\xff\xac" +static inline size_t reflection_Enum_vec_len(reflection_Enum_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_Enum_table_t reflection_Enum_vec_at(reflection_Enum_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_Enum_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_Enum) + +__flatbuffers_define_string_field(0, reflection_Enum, name, 1) +__flatbuffers_define_find_by_string_field(reflection_Enum, name) +__flatbuffers_define_sort_by_string_field(reflection_Enum, name) +__flatbuffers_define_default_find_by_string_field(reflection_Enum, name) +__flatbuffers_define_default_scan_by_string_field(reflection_Enum, name) +#define reflection_Enum_vec_sort reflection_Enum_vec_sort_by_name +__flatbuffers_define_vector_field(1, reflection_Enum, values, reflection_EnumVal_vec_t, 1) +__flatbuffers_define_scalar_field(2, reflection_Enum, is_union, flatbuffers_bool, flatbuffers_bool_t, UINT8_C(0)) +__flatbuffers_define_table_field(3, reflection_Enum, underlying_type, reflection_Type_table_t, 1) + +struct reflection_Field_table { uint8_t unused__; }; + +#ifndef reflection_Field_identifier +#define reflection_Field_identifier flatbuffers_identifier +#endif +#define reflection_Field_type_hash ((flatbuffers_thash_t)0x9f7e408a) +#define reflection_Field_type_identifier "\x8a\x40\x7e\x9f" +static inline size_t reflection_Field_vec_len(reflection_Field_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_Field_table_t reflection_Field_vec_at(reflection_Field_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_Field_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_Field) + +__flatbuffers_define_string_field(0, reflection_Field, name, 1) +__flatbuffers_define_find_by_string_field(reflection_Field, name) +__flatbuffers_define_sort_by_string_field(reflection_Field, name) +__flatbuffers_define_default_find_by_string_field(reflection_Field, name) +__flatbuffers_define_default_scan_by_string_field(reflection_Field, name) +#define reflection_Field_vec_sort reflection_Field_vec_sort_by_name +__flatbuffers_define_table_field(1, reflection_Field, type, reflection_Type_table_t, 1) +__flatbuffers_define_scalar_field(2, reflection_Field, id, flatbuffers_uint16, uint16_t, UINT16_C(0)) +__flatbuffers_define_scalar_field(3, reflection_Field, offset, flatbuffers_uint16, uint16_t, UINT16_C(0)) +__flatbuffers_define_scalar_field(4, reflection_Field, default_integer, flatbuffers_int64, int64_t, INT64_C(0)) +__flatbuffers_define_scalar_field(5, reflection_Field, default_real, flatbuffers_double, double, 0.000000) +__flatbuffers_define_scalar_field(6, reflection_Field, deprecated, flatbuffers_bool, flatbuffers_bool_t, UINT8_C(0)) +__flatbuffers_define_scalar_field(7, reflection_Field, required, flatbuffers_bool, flatbuffers_bool_t, UINT8_C(0)) +__flatbuffers_define_scalar_field(8, reflection_Field, key, flatbuffers_bool, flatbuffers_bool_t, UINT8_C(0)) + +struct reflection_Object_table { uint8_t unused__; }; + +#ifndef reflection_Object_identifier +#define reflection_Object_identifier flatbuffers_identifier +#endif +#define reflection_Object_type_hash ((flatbuffers_thash_t)0xb09729bd) +#define reflection_Object_type_identifier "\xbd\x29\x97\xb0" +static inline size_t reflection_Object_vec_len(reflection_Object_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_Object_table_t reflection_Object_vec_at(reflection_Object_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_Object_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_Object) + +__flatbuffers_define_string_field(0, reflection_Object, name, 1) +__flatbuffers_define_find_by_string_field(reflection_Object, name) +__flatbuffers_define_sort_by_string_field(reflection_Object, name) +__flatbuffers_define_default_find_by_string_field(reflection_Object, name) +__flatbuffers_define_default_scan_by_string_field(reflection_Object, name) +#define reflection_Object_vec_sort reflection_Object_vec_sort_by_name +__flatbuffers_define_vector_field(1, reflection_Object, fields, reflection_Field_vec_t, 1) +__flatbuffers_define_scalar_field(2, reflection_Object, is_struct, flatbuffers_bool, flatbuffers_bool_t, UINT8_C(0)) +__flatbuffers_define_scalar_field(3, reflection_Object, minalign, flatbuffers_int32, int32_t, INT32_C(0)) +__flatbuffers_define_scalar_field(4, reflection_Object, bytesize, flatbuffers_int32, int32_t, INT32_C(0)) + +struct reflection_Schema_table { uint8_t unused__; }; + +#ifndef reflection_Schema_identifier +#define reflection_Schema_identifier flatbuffers_identifier +#endif +#define reflection_Schema_type_hash ((flatbuffers_thash_t)0xfaf93779) +#define reflection_Schema_type_identifier "\x79\x37\xf9\xfa" +static inline size_t reflection_Schema_vec_len(reflection_Schema_vec_t vec) +__flatbuffers_vec_len(vec) +static inline reflection_Schema_table_t reflection_Schema_vec_at(reflection_Schema_vec_t vec, size_t i) +__flatbuffers_offset_vec_at(reflection_Schema_table_t, vec, i, 0) +__flatbuffers_table_as_root(reflection_Schema) + +__flatbuffers_define_vector_field(0, reflection_Schema, objects, reflection_Object_vec_t, 1) +__flatbuffers_define_vector_field(1, reflection_Schema, enums, reflection_Enum_vec_t, 1) +__flatbuffers_define_string_field(2, reflection_Schema, file_ident, 0) +__flatbuffers_define_string_field(3, reflection_Schema, file_ext, 0) +__flatbuffers_define_table_field(4, reflection_Schema, root_table, reflection_Object_table_t, 0) + +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* REFLECTION_READER_H */ diff --git a/Source/library/FBSUtil/flatcc/reflection/reflection_verifier.h b/Source/library/FBSUtil/flatcc/reflection/reflection_verifier.h new file mode 100644 index 0000000..3ec9d00 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/reflection/reflection_verifier.h @@ -0,0 +1,206 @@ +#ifndef REFLECTION_VERIFIER_H +#define REFLECTION_VERIFIER_H + +/* Generated by flatcc 0.4.2 FlatBuffers schema compiler for C by dvide.com */ + +#ifndef REFLECTION_READER_H +#include "reflection_reader.h" +#endif +#include "flatcc/flatcc_verifier.h" +#define PDIAGNOSTIC_IGNORE_UNUSED +#include "flatcc/portable/pdiagnostic_push.h" + +static int __reflection_Type_table_verifier(flatcc_table_verifier_descriptor_t *td); +static int __reflection_EnumVal_table_verifier(flatcc_table_verifier_descriptor_t *td); +static int __reflection_Enum_table_verifier(flatcc_table_verifier_descriptor_t *td); +static int __reflection_Field_table_verifier(flatcc_table_verifier_descriptor_t *td); +static int __reflection_Object_table_verifier(flatcc_table_verifier_descriptor_t *td); +static int __reflection_Schema_table_verifier(flatcc_table_verifier_descriptor_t *td); + +static int __reflection_Type_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_field(td, 0, 1, 1) /* base_type */)) return ret; + if ((ret = flatcc_verify_field(td, 1, 1, 1) /* element */)) return ret; + if ((ret = flatcc_verify_field(td, 2, 4, 4) /* index */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_Type_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Type_identifier, &__reflection_Type_table_verifier); +} + +static inline int reflection_Type_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Type_type_identifier, &__reflection_Type_table_verifier); +} + +static inline int reflection_Type_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_Type_table_verifier); +} + +static inline int reflection_Type_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_Type_table_verifier); +} + +static int __reflection_EnumVal_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_string_field(td, 0, 1) /* name */)) return ret; + if ((ret = flatcc_verify_field(td, 1, 8, 8) /* value */)) return ret; + if ((ret = flatcc_verify_table_field(td, 2, 0, &__reflection_Object_table_verifier) /* object */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_EnumVal_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_EnumVal_identifier, &__reflection_EnumVal_table_verifier); +} + +static inline int reflection_EnumVal_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_EnumVal_type_identifier, &__reflection_EnumVal_table_verifier); +} + +static inline int reflection_EnumVal_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_EnumVal_table_verifier); +} + +static inline int reflection_EnumVal_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_EnumVal_table_verifier); +} + +static int __reflection_Enum_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_string_field(td, 0, 1) /* name */)) return ret; + if ((ret = flatcc_verify_table_vector_field(td, 1, 1, &__reflection_EnumVal_table_verifier) /* values */)) return ret; + if ((ret = flatcc_verify_field(td, 2, 1, 1) /* is_union */)) return ret; + if ((ret = flatcc_verify_table_field(td, 3, 1, &__reflection_Type_table_verifier) /* underlying_type */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_Enum_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Enum_identifier, &__reflection_Enum_table_verifier); +} + +static inline int reflection_Enum_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Enum_type_identifier, &__reflection_Enum_table_verifier); +} + +static inline int reflection_Enum_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_Enum_table_verifier); +} + +static inline int reflection_Enum_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_Enum_table_verifier); +} + +static int __reflection_Field_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_string_field(td, 0, 1) /* name */)) return ret; + if ((ret = flatcc_verify_table_field(td, 1, 1, &__reflection_Type_table_verifier) /* type */)) return ret; + if ((ret = flatcc_verify_field(td, 2, 2, 2) /* id */)) return ret; + if ((ret = flatcc_verify_field(td, 3, 2, 2) /* offset */)) return ret; + if ((ret = flatcc_verify_field(td, 4, 8, 8) /* default_integer */)) return ret; + if ((ret = flatcc_verify_field(td, 5, 8, 8) /* default_real */)) return ret; + if ((ret = flatcc_verify_field(td, 6, 1, 1) /* deprecated */)) return ret; + if ((ret = flatcc_verify_field(td, 7, 1, 1) /* required */)) return ret; + if ((ret = flatcc_verify_field(td, 8, 1, 1) /* key */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_Field_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Field_identifier, &__reflection_Field_table_verifier); +} + +static inline int reflection_Field_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Field_type_identifier, &__reflection_Field_table_verifier); +} + +static inline int reflection_Field_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_Field_table_verifier); +} + +static inline int reflection_Field_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_Field_table_verifier); +} + +static int __reflection_Object_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_string_field(td, 0, 1) /* name */)) return ret; + if ((ret = flatcc_verify_table_vector_field(td, 1, 1, &__reflection_Field_table_verifier) /* fields */)) return ret; + if ((ret = flatcc_verify_field(td, 2, 1, 1) /* is_struct */)) return ret; + if ((ret = flatcc_verify_field(td, 3, 4, 4) /* minalign */)) return ret; + if ((ret = flatcc_verify_field(td, 4, 4, 4) /* bytesize */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_Object_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Object_identifier, &__reflection_Object_table_verifier); +} + +static inline int reflection_Object_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Object_type_identifier, &__reflection_Object_table_verifier); +} + +static inline int reflection_Object_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_Object_table_verifier); +} + +static inline int reflection_Object_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_Object_table_verifier); +} + +static int __reflection_Schema_table_verifier(flatcc_table_verifier_descriptor_t *td) +{ + int ret; + if ((ret = flatcc_verify_table_vector_field(td, 0, 1, &__reflection_Object_table_verifier) /* objects */)) return ret; + if ((ret = flatcc_verify_table_vector_field(td, 1, 1, &__reflection_Enum_table_verifier) /* enums */)) return ret; + if ((ret = flatcc_verify_string_field(td, 2, 0) /* file_ident */)) return ret; + if ((ret = flatcc_verify_string_field(td, 3, 0) /* file_ext */)) return ret; + if ((ret = flatcc_verify_table_field(td, 4, 0, &__reflection_Object_table_verifier) /* root_table */)) return ret; + return flatcc_verify_ok; +} + +static inline int reflection_Schema_verify_as_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Schema_identifier, &__reflection_Schema_table_verifier); +} + +static inline int reflection_Schema_verify_as_typed_root(const void *buf, size_t bufsiz) +{ + return flatcc_verify_table_as_root(buf, bufsiz, reflection_Schema_type_identifier, &__reflection_Schema_table_verifier); +} + +static inline int reflection_Schema_verify_as_root_with_identifier(const void *buf, size_t bufsiz, const char *fid) +{ + return flatcc_verify_table_as_root(buf, bufsiz, fid, &__reflection_Schema_table_verifier); +} + +static inline int reflection_Schema_verify_as_root_with_type_hash(const void *buf, size_t bufsiz, flatbuffers_thash_t thash) +{ + return flatcc_verify_table_as_typed_root(buf, bufsiz, thash, &__reflection_Schema_table_verifier); +} + +#include "flatcc/portable/pdiagnostic_pop.h" +#endif /* REFLECTION_VERIFIER_H */ diff --git a/Source/library/FBSUtil/flatcc/support/README b/Source/library/FBSUtil/flatcc/support/README new file mode 100644 index 0000000..d9f6ec0 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/support/README @@ -0,0 +1 @@ +support files mainly used for testing diff --git a/Source/library/FBSUtil/flatcc/support/cdump.h b/Source/library/FBSUtil/flatcc/support/cdump.h new file mode 100644 index 0000000..56d48a0 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/support/cdump.h @@ -0,0 +1,31 @@ +#ifndef CDUMP_H +#define CDUMP_H + +#include + +/* Generates a constant a C byte array. */ +static void cdump(char *name, void *addr, size_t len, FILE *fp) { + unsigned int i; + unsigned char buff[17]; + unsigned char *pc = (unsigned char*)addr; + + // Output description if given. + name = name ? name : "dump"; + fprintf(fp, "const unsigned char %s[] = {", name); + + // Process every byte in the data. + for (i = 0; i < (unsigned int)len; i++) { + // Multiple of 16 means new line (with line offset). + + if ((i % 16) == 0) { + fprintf(fp, "\n "); + } else if ((i % 8) == 0) { + fprintf(fp, " "); + } + + fprintf(fp, " 0x%02x,", pc[i]); + } + fprintf(fp, "\n};\n"); +} + +#endif /* CDUMP_H */ diff --git a/Source/library/FBSUtil/flatcc/support/elapsed.h b/Source/library/FBSUtil/flatcc/support/elapsed.h new file mode 100644 index 0000000..f89663a --- /dev/null +++ b/Source/library/FBSUtil/flatcc/support/elapsed.h @@ -0,0 +1,65 @@ +#ifndef ELAPSED_H +#define ELAPSED_H + +#include + +/* Based on http://stackoverflow.com/a/8583395 */ +#if !defined(_WIN32) +#include +static double elapsed_realtime(void) { // returns 0 seconds first time called + static struct timeval t0; + struct timeval tv; + gettimeofday(&tv, 0); + if (!t0.tv_sec) + t0 = tv; + return tv.tv_sec - t0.tv_sec + (tv.tv_usec - t0.tv_usec) / 1000000.; +} +#else +#include +#ifndef FatalError +#define FatalError(s) do { perror(s); exit(-1); } while(0) +#endif +static double elapsed_realtime(void) { // granularity about 50 microsecs on my machine + static LARGE_INTEGER freq, start; + LARGE_INTEGER count; + if (!QueryPerformanceCounter(&count)) + FatalError("QueryPerformanceCounter"); + if (!freq.QuadPart) { // one time initialization + if (!QueryPerformanceFrequency(&freq)) + FatalError("QueryPerformanceFrequency"); + start = count; + } + return (double)(count.QuadPart - start.QuadPart) / freq.QuadPart; +} +#endif + +/* end Based on stackoverflow */ + +static int show_benchmark(const char *descr, double t1, double t2, size_t size, int rep, const char *reptext) +{ + double tdiff = t2 - t1; + double nstime; + + printf("operation: %s\n", descr); + printf("elapsed time: %.3f (s)\n", tdiff); + printf("iterations: %d\n", rep); + printf("size: %lu (bytes)\n", (unsigned long)size); + printf("bandwidth: %.3f (MB/s)\n", (double)rep * size / 1e6 / tdiff); + printf("throughput in ops per sec: %.3f\n", rep / tdiff); + if (reptext && rep != 1) { + printf("throughput in %s ops per sec: %.3f\n", reptext, 1 / tdiff); + } + nstime = tdiff * 1e9 / rep; + if (nstime < 1000) { + printf("time per op: %.3f (ns)\n", nstime); + } else if (nstime < 1e6) { + printf("time per op: %.3f (us)\n", nstime / 1000); + } else if (nstime < 1e9) { + printf("time per op: %.3f (ms)\n", nstime / 1e6); + } else { + printf("time per op: %.3f (s)\n", nstime / 1e9); + } + return 0; +} + +#endif /* ELAPSED_H */ diff --git a/Source/library/FBSUtil/flatcc/support/hexdump.h b/Source/library/FBSUtil/flatcc/support/hexdump.h new file mode 100644 index 0000000..430ec68 --- /dev/null +++ b/Source/library/FBSUtil/flatcc/support/hexdump.h @@ -0,0 +1,52 @@ +#ifndef HEXDUMP_H +#define HEXDUMP_H + +#include + +/* Based on: http://stackoverflow.com/a/7776146 */ +static void hexdump(char *desc, void *addr, size_t len, FILE *fp) { + unsigned int i; + unsigned char buff[17]; + unsigned char *pc = (unsigned char*)addr; + + // Output description if given. + if (desc != NULL) + fprintf(fp, "%s:\n", desc); + + // Process every byte in the data. + for (i = 0; i < (unsigned int)len; i++) { + // Multiple of 16 means new line (with line offset). + + if ((i % 16) == 0) { + // Just don't print ASCII for the zeroth line. + if (i != 0) + fprintf(fp, " %s\n", buff); + + // Output the offset. + fprintf(fp, " %04x ", i); + } else if ((i % 8) == 0) { + fprintf(fp, " "); + } + + // Now the hex code for the specific character. + fprintf(fp, " %02x", pc[i]); + + // And store a printable ASCII character for later. + if ((pc[i] < 0x20) || (pc[i] > 0x7e)) + buff[i % 16] = '.'; + else + buff[i % 16] = pc[i]; + buff[(i % 16) + 1] = '\0'; + } + + // Pad out last line if not exactly 16 characters. + while ((i % 16) != 0) { + fprintf(fp, " "); + i++; + } + + // And print the final ASCII bit. + fprintf(fp, " %s\n", buff); +} + +#endif /* HEXDUMP_H */ diff --git a/Source/library/FBSUtil/flatcc/support/readfile.h b/Source/library/FBSUtil/flatcc/support/readfile.h new file mode 100644 index 0000000..680252b --- /dev/null +++ b/Source/library/FBSUtil/flatcc/support/readfile.h @@ -0,0 +1,55 @@ +#ifndef READFILE_H +#define READFILE_H + +#include +#include + +static char *readfile(const char *filename, size_t max_size, size_t *size_out) +{ + FILE *fp; + size_t size, pos, n, _out; + char *buf; + + size_out = size_out ? size_out : &_out; + + fp = fopen(filename, "rb"); + size = 0; + buf = 0; + + if (!fp) { + goto fail; + } + fseek(fp, 0L, SEEK_END); + size = ftell(fp); + *size_out = size; + if (max_size > 0 && size > max_size) { + goto fail; + } + rewind(fp); + buf = malloc(size ? size : 1); + if (!buf) { + goto fail; + } + pos = 0; + while ((n = fread(buf + pos, 1, size - pos, fp))) { + pos += n; + } + if (pos != size) { + goto fail; + } + fclose(fp); + *size_out = size; + return buf; + +fail: + if (fp) { + fclose(fp); + } + if (buf) { + free(buf); + } + *size_out = size; + return 0; +} + +#endif /* READFILE_H */ diff --git a/Source/library/FBSUtil/module.modulemap b/Source/library/FBSUtil/module.modulemap new file mode 100644 index 0000000..f7daa11 --- /dev/null +++ b/Source/library/FBSUtil/module.modulemap @@ -0,0 +1,4 @@ +module LibFBSUtil [system][extern_c] { + header "FBSUtil.h" + export * +} diff --git a/Source/library/README.md b/Source/library/README.md new file mode 100644 index 0000000..0a79d11 --- /dev/null +++ b/Source/library/README.md @@ -0,0 +1,4 @@ +# C Library +This folder contains C modules used in Serrano. + +- FBSUtil: A C library based on [flatcc](https://github.com/dvidelabs/flatcc) offering APIs read/write flatbuffers. \ No newline at end of file diff --git a/Tests/MacTestHostingApp/AppDelegate.swift b/Tests/MacTestHostingApp/AppDelegate.swift new file mode 100644 index 0000000..63d5be2 --- /dev/null +++ b/Tests/MacTestHostingApp/AppDelegate.swift @@ -0,0 +1,26 @@ +// +// AppDelegate.swift +// MacTestHostingApp +// +// Created by ZHONGHAO LIU on 8/15/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Cocoa + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Insert code here to initialize your application + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + + +} + diff --git a/Tests/MacTestHostingApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/MacTestHostingApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2db2b1c --- /dev/null +++ b/Tests/MacTestHostingApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "16x16", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "32x32", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "128x128", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "256x256", + "scale" : "2x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "1x" + }, + { + "idiom" : "mac", + "size" : "512x512", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tests/MacTestHostingApp/Base.lproj/Main.storyboard b/Tests/MacTestHostingApp/Base.lproj/Main.storyboard new file mode 100644 index 0000000..5e7e3a6 --- /dev/null +++ b/Tests/MacTestHostingApp/Base.lproj/Main.storyboard @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/MacTestHostingApp/Info.plist b/Tests/MacTestHostingApp/Info.plist new file mode 100644 index 0000000..f219fe5 --- /dev/null +++ b/Tests/MacTestHostingApp/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + Copyright © 2017 ZHONGHAO LIU. All rights reserved. + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + + diff --git a/Tests/MacTestHostingApp/ViewController.swift b/Tests/MacTestHostingApp/ViewController.swift new file mode 100644 index 0000000..8f01a25 --- /dev/null +++ b/Tests/MacTestHostingApp/ViewController.swift @@ -0,0 +1,27 @@ +// +// ViewController.swift +// MacTestHostingApp +// +// Created by ZHONGHAO LIU on 8/15/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Cocoa + +class ViewController: NSViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + override var representedObject: Any? { + didSet { + // Update the view, if already loaded. + } + } + + +} + diff --git a/Tests/README.md b/Tests/README.md new file mode 100644 index 0000000..c1c9557 --- /dev/null +++ b/Tests/README.md @@ -0,0 +1 @@ +Since Metal library is not included in Xcode simulator. We need create a Host Applciation so that we can do Metal related Uit testing on iOS device. \ No newline at end of file diff --git a/Tests/SerranoMacTests/Info.plist b/Tests/SerranoMacTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/Tests/SerranoMacTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Tests/SerranoMacTests/SerranoMacTests.swift b/Tests/SerranoMacTests/SerranoMacTests.swift new file mode 100644 index 0000000..caa8988 --- /dev/null +++ b/Tests/SerranoMacTests/SerranoMacTests.swift @@ -0,0 +1,35 @@ +// +// SerranoMacTests.swift +// SerranoMacTests +// +// Created by ZHONGHAO LIU on 8/15/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +class SerranoMacTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Tests/SerranoTests/Supporting Files/Info.plist b/Tests/SerranoTests/Supporting Files/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/Tests/SerranoTests/Supporting Files/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/Tests/SerranoTests/core/engine_test.swift b/Tests/SerranoTests/core/engine_test.swift new file mode 100644 index 0000000..9819cf3 --- /dev/null +++ b/Tests/SerranoTests/core/engine_test.swift @@ -0,0 +1,123 @@ +// +// engine_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Metal +@testable import Serrano + +class engine_test: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + +// func testExample() { +// // This is an example of a functional test case. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// +//// test_configure_engine() +//// testLoadingKernel() +//// testLoadingKernel() +// } + +// func testPerformanceExample() { +// // This is an example of a performance test case. +// self.measure { +// // Put the code you want to measure the time of here. +// } +// } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Test function: configureEngine() + + - Note: This test needs to be running on physical iOS device with METAL support. + */ + func test_configure_GPUEngine_default() { + let engine = SerranoEngine.configuredEngine + engine.resetEngine() + let (success, msg) = engine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + if !success { + print("No GPU available. GIVE up test") + return + } + XCTAssertTrue(success, "Fail test:\(msg)") + + // attributes + XCTAssertNotNil(engine.GPUDevice) + XCTAssertNotNil(engine.metalLibrary) + XCTAssertNotNil(engine.serranoCommandQueue) + } + + func test_configure_GPUEngine_fromUserParams() { + let engine = SerranoEngine.configuredEngine + engine.resetEngine() + let GPUDevice = MTLCreateSystemDefaultDevice() + guard GPUDevice != nil else { + print("NO gpu available. Give up test") + return + } + + let (success, msg) = engine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: GPUDevice!) + XCTAssertTrue(success, "Fail test:\(msg)") + + // attributes + XCTAssertNotNil(engine.GPUDevice) + XCTAssertNotNil(engine.metalLibrary) + XCTAssertNotNil(engine.serranoCommandQueue) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func loadGPUKernel(kernelLabel label: String) -> (result: MTLComputePipelineState?, message: String) + */ + func test_loading_kernels() { + let engine = SerranoEngine.configuredEngine + engine.resetEngine() + let (success, msg) = engine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + if !success { + print("No gpu available. Give up test") + return + } + + // precondition + XCTAssertTrue(success, "Fail test:\(msg)") + XCTAssertNotNil(engine.metalLibrary) + + // All functions in serrano's default metal library should be successfully load + print("\n\nTesting function loading....") + for funcName in engine.metalLibrary!.functionNames { + let (kernel, msg) = engine.loadGPUKernel(kernelLabel: funcName) + XCTAssertNotNil(kernel, "Fail test: \(msg)") + print("Pass! Successfully Load kernel \(funcName)") + } + + + // Randomly generate function name + print("\n\nTesting random function loading....") + for i in 0..<10 { + let randomFuncName = randomString(length: i+1) + let (kernel, msg) = engine.loadGPUKernel(kernelLabel: randomFuncName) + XCTAssertNil(kernel, "Fail test: \(msg)") + print("Pass! Random function name: \(randomFuncName)") + + } + + } + + +} diff --git a/Tests/SerranoTests/core/resource_manager_test.swift b/Tests/SerranoTests/core/resource_manager_test.swift new file mode 100644 index 0000000..71f2083 --- /dev/null +++ b/Tests/SerranoTests/core/resource_manager_test.swift @@ -0,0 +1,108 @@ +// +// resource_manager_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/9/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class resource_manager_test: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + /** + Target: + public func allocateTensors(forShapes shapes: [TensorShape]) -> [Tensor] + public func returnTensors(_ tensors: [Tensor]) + public func isManagingTensor(_ tensor: Tensor) -> Bool + public func isTensorAvailable(_ tensor: Tensor) -> Bool + */ + func testTensorManagement() { + let numCase = 100 + let manager = SerranoResourceManager() + + for i in 0...stride + while shape2NeedBytes > tensor1AllocateBytes { + shape2 = randomShape(dimensions: 2, dimensionSizeRange: [40, 50], dataType: .float) + shape2NeedBytes = shape2.shapeArray.reduce(1, *) * MemoryLayout.stride + } + print("Tensor 2 require bytes: \(shape2NeedBytes), tensor 1 allocate bytes: \(tensor1AllocateBytes)") + } else { + // generate shape2 should be larger than shape1 + var shape2NeedBytes = shape2.shapeArray.reduce(1, *) * MemoryLayout.stride + while shape2NeedBytes <= tensor1AllocateBytes { + shape2 = randomShape(dimensions: 2, dimensionSizeRange: [200, 210], dataType: .float) + shape2NeedBytes = shape2.shapeArray.reduce(1, *) * MemoryLayout.stride + } + print("Tensor 2 require bytes: \(shape2NeedBytes), tensor 1 allocate bytes: \(tensor1AllocateBytes)") + } + + + let newTensor2 = manager.allocateTensors([shape2]).first + XCTAssertNotNil(newTensor2) + XCTAssertTrue(newTensor2!.shape == shape2) + XCTAssertTrue(manager.isManagingTensor(newTensor2!)) + XCTAssertTrue(!manager.isTensorAvailable(newTensor2!)) + + if i % 2 == 0 { + // should reuse + XCTAssertTrue(!manager.isTensorAvailable(newTensor!)) + XCTAssertTrue(newTensor2! == newTensor!) + } else { + // should allocate a new tensor + XCTAssertTrue(manager.isTensorAvailable(newTensor!)) + XCTAssertTrue(newTensor2 != newTensor!) + print(newTensor!.description) + print(newTensor2!.description) + } + + + manager.releaseAllResources() + + print("Finish Test \(i+1)\n\n\n") + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func allocateMTLBuffers(forTensors tensors: [Tensor]) -> [MTLBuffer] + public func releaseTensorAttachedBuffers(_ tensors: [Tensor]) + public func isManagingBufferr(_ buffer: MTLBuffer) -> Bool + */ + func testBufferManagement() { + // TODO: + } + +} diff --git a/Tests/SerranoTests/core/tensor_test.swift b/Tests/SerranoTests/core/tensor_test.swift new file mode 100644 index 0000000..2b17612 --- /dev/null +++ b/Tests/SerranoTests/core/tensor_test.swift @@ -0,0 +1,811 @@ +// +// tensor_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Metal +@testable import Serrano + +class TensorTest: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + +// func testExample() { +// // This is an example of a functional test case. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// +// } +// +// func testPerformanceExample() { +// // This is an example of a performance test case. +// self.measure { +// // Put the code you want to measure the time of here. +// } +// } + + + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + Targets: + Tensor.init(dataArray array:[Any], tensorShape shape: TensorShape) + flatArrayFloat() -> [Float] + */ + func testCreateTensor() { + let numCase = 100 + //TODO: more test + for _ in 0.. [Any] + func constructNestedArrayFrom(location: [Int]) -> [Any] + */ + func testNestedFunc() { + let numCase = 100 + + for _ in 0.. [Int] { + var indices = [Int]() + for dimSize in shape { + if valid { + indices.append(randomInt([0, dimSize-1])) + } else { + indices.append(randomInt([-1, dimSize+3])) + } + } + return indices + } + + /** + Target: + func indexIsValid(_ index: [Int]) -> Bool + */ + func testIndexValid() { + let numCase = 100 + + for _ in 0.. Float + */ + func testSubscript() { + let numCase = 100 + + for _ in 0..(OpaquePointer(bufferAddress)) + let mtlBufferReader = UnsafeMutableBufferPointer(start: mtlBufferPointer, count: tensor.capacity) + for index in 0.. Tensor? + */ + func testTensorBatchSlice() { + let numCase = 100 + for i in 0.. ComputationGraph { + let g = ComputationGraph() + + // input tensors + var dataTensoSymbols = [TensorSymbol]() + let shape = TensorShape(dataType: .float, shape: [randomInt([100, 100]), randomInt([100, 100]), 3]) + for _ in 0.. TensorSymbol + */ + func testTensor() { + let numCase = 100 + let graph = ComputationGraph() + + for i in 0.. ScalarSymbol + */ + func testScalar() { + let numCase = 100 + let graph = ComputationGraph() + + for i in 0.. ([TensorSymbol], OperatorSymbol) + */ + func testOperation() { + let numCase = 100 + let graph = ComputationGraph() + + for i in 0.. Bool in + symbol.UID == tensorSymbol.UID + })) + XCTAssertTrue(tensorSymbol.outBounds.contains(where: { (symbol) -> Bool in + symbol.UID == opSymbol.UID + })) + } + + // check bounds between outSymbol && opSymbol + for tensorSymbol in outSymbols { + XCTAssertTrue(opSymbol.outBounds.contains(where: { (symbol) -> Bool in + symbol.UID == tensorSymbol.UID + })) + XCTAssertTrue(tensorSymbol.inBounds.contains(where: { (symbol) -> Bool in + symbol.UID == opSymbol.UID + })) + } + + // check bounds between opSymbol and parameter symbol if available + if i % 3 == 2 { + XCTAssertTrue(opSymbol.paramSymbols.count > 0) + for paramSymbol in opSymbol.paramSymbols { + XCTAssertTrue(opSymbol.inBounds.contains(where: { (symbol) -> Bool in + symbol.UID == paramSymbol.UID + })) + XCTAssertTrue(paramSymbol.outBounds.contains(where: { (symbol) -> Bool in + symbol.UID == opSymbol.UID + })) + } + } + + print("Finish test case \(i+1)\n\n") + } + + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func sortGraph() + */ + func testSortGraph() { + let numCase = 100 + for i in 0.. (valid: Bool, msg: String) + */ + func testUserInputBindCheck() { + let numCase = 100 + for i in 0.. (valid: Bool, msg: String) + internal func checkShapeChain() -> (valid: Bool, msg: String) + */ + func testVerifyGraph() { + let numCase = 50 + for i in 0.. [Tensor]? + internal func stageOrderCalculate(mode: OperatorComputationMode) + */ + func testForward() { + let numCase = 50 + + let _ = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU) + + for i in 0.. Graph in extension + */ + func testGraphGenerate() { + //TODO: test + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func serranoSymbolUIDGenerate() -> String + */ + func testSerranoSymbolUIDGenerate() { + let numCase = 10000 + var UIDSet = Set() + + for i in 0.. Bool in + return symbol.UID == inputA.UID + })) + XCTAssertTrue(opSymbol.inputSymbols.contains(where: { (symbol) -> Bool in + return symbol.UID == inputB.UID + })) + + // inbouds + XCTAssertTrue(opSymbol.inBounds.contains(where: { (symbol) -> Bool in + return symbol.UID == inputA.UID + })) + XCTAssertTrue(opSymbol.inBounds.contains(where: { (symbol) -> Bool in + return symbol.UID == inputB.UID + })) + + print("Finish test case \(i+1)\n\n") + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func outputSymbols() -> [TensorSymbol] + */ + func testOutputSymbols() { + let numCase = 100 + for i in 0..(inputSymbols[0..<2])) + outputSymbols = opSymbol.outputSymbols() + } + + // get output symbols checking + var outputSymbolsCheck = [TensorSymbol]() + let inputShapes = opSymbol.inputSymbols.map({ (symbol) -> TensorShape in + return symbol.shape + }) + let outputShapes = opSymbol.serranoOperator.outputShape(shapeArray: inputShapes) + for outputShape in outputShapes! { + outputSymbolsCheck.append(SerranoTensorSymbol(dataSource: .User, shape: outputShape)) + } + + // CHECK + XCTAssertTrue(outputSymbolsCheck.count == outputSymbols.count) + for (outSymbolCheck, outSymbol) in zip(outputSymbolsCheck, outputSymbols) { + print("Out symbol check:", outSymbolCheck.shape.description) + print("Out symbol:", outSymbol.shape.description) + XCTAssertTrue(outSymbolCheck.shape == outSymbol.shape) + } + + print("Finish test case \(i+1)\n\n") + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + public func addToParamSymbols(_ symbol: GraphSymbol) + */ + func testAddToParamSymbols() { + let numCase = 100 + for i in 0.. Bool + */ + func testBindData() { + let numCase = 100 + for i in 0.. Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + for i in 0..() + testCase.testAll() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// func testClose() { +// let op = AddOperator() +// // generate tensors +// var inputTensors = [Tensor]() +// var outputTensors = [Tensor]() +// var shape: TensorShape +// +// // configure engine +// let (_, msg) = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) +// +// shape = randomShape(dimensions: 2, dimensionSizeRange: [2000, 2000], dataType: .float) +// for _ in 0..<2 { +// inputTensors.append(randomTensor(fromShape: shape)) +// print("Generate Input tensor: \(inputTensors.last!.description)") +// } +// outputTensors.append(randomTensor(fromShape: shape)) +// +// op.inputTensors = inputTensors +// op.outputTensors = outputTensors +// +// self.measure { +// op.compute(.GPU) +// } +// +// SerranoResourceManager.globalManager.releaseAllResources() +// } + +// func testAd() { +// let (_,_) = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU) +// let tensorA = Tensor(randomRampTensor: TensorShape(dataType: .float, shape: [2,4])) +// let tensorB = Tensor(randomRampTensor: TensorShape(dataType: .float, shape: [2,4])) +// let tensorC = Tensor(randomRampTensor: TensorShape(dataType: .float, shape: [2,4])) +// let op = AddOperator(inputTensors: [tensorA, tensorB], outputTensors: [tensorC]) +// op.compute(.GPU) +// +// } +} + diff --git a/Tests/SerranoTests/op/basic/binary_op/binary_op_test.swift b/Tests/SerranoTests/op/basic/binary_op/binary_op_test.swift new file mode 100644 index 0000000..0b591e2 --- /dev/null +++ b/Tests/SerranoTests/op/basic/binary_op/binary_op_test.swift @@ -0,0 +1,208 @@ +// +// binary_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/7/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Dispatch +import Metal +@testable import Serrano + + +public class OperatorDelegateConvBinaryOp: OperatorDelegateConv { + + public var compareBlock: ([Tensor], Tensor) -> Void + + required public convenience init(compareBlock: (([Tensor], Tensor) -> Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + print("NEED OVERRIDE") + } + self.init(block: blcok) + } + + // override this func + public init(block: @escaping ([Tensor], Tensor) -> Void) { + self.compareBlock = block + super.init() + } + + override public func compare() { + XCTAssertTrue(self.resultTensors.count == 1) + XCTAssertTrue(self.resultTensors.first!.count == self.veryfyTensors.first!.count) + + self.compareBlock(self.veryfyTensors, self.resultTensors.first!) + } +} + +public class BinaryOpTest: XCTestCase { + + override public func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override public func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + // func testExample() { + // // This is an example of a functional test case. + // // Use XCTAssert and related functions to verify your tests produce the correct results. + // } + // + // func testPerformanceExample() { + // // This is an example of a performance test case. + // self.measure { + // // Put the code you want to measure the time of here. + // } + // } + + public func testAll() { + self.testInit() + + self.testOuputShapesCheck() + + // self.testCPU() + // + // self.testGPU() + // + self.testCompute() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Test init functions + */ + func testInit() { + let numCase = 100 + let op = BinaryOp() + for _ in 0.. [TensorShape]? + */ + func testOuputShapesCheck() { + let numCase = 100 + for i in 0.. [Tensor] + func compute(asyncWithInputTensors tensors:[Tensor], computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) + */ + + func testCompute() { + let caseNum = 10 + let op = BinaryOp() + + // configure engine + let (_, _) = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + + // setup delegate + let delegate = OpDelegate(compareBlock: nil) + let workingGroup = DispatchGroup() + delegate.dispatchGroup = workingGroup + op.computationDelegate = delegate + + + for i in 0.. Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + var val:Float + for i in 0..() + testCase.testAll() + } + +// func testRecip() { +// let tensor = randomTensor(fromShape: TensorShape(dataType: .int, shape: [2, 5])) +// let tensorb = randomTensor(fromShape: TensorShape(dataType: .int, shape: [2, 5])) +// let tensorc = randomTensor(fromShape: TensorShape(dataType: .int, shape: [2, 5])) +// var countB = Int32(tensor.count) +// print(tensor.flatArrayFloat()) +// print(tensorb.flatArrayFloat()) +//// vvrecf(tensorb.contentsAddress, tensor.contentsAddress, &countB) +// +// +// cblas_ssbmv(CblasRowMajor, CblasLower, countB, 0, 1.0, tensor.contentsAddress, 1, tensorb.contentsAddress, 1, 0.0, tensor.contentsAddress, 1) +// +// print(tensor.flatArrayFloat()) +// print(tensorb.flatArrayFloat()) +//// print(tensorc.flatArrayFloat()) +// } +} diff --git a/Tests/SerranoTests/op/basic/binary_op/mult_op_test.swift b/Tests/SerranoTests/op/basic/binary_op/mult_op_test.swift new file mode 100644 index 0000000..60b1ad5 --- /dev/null +++ b/Tests/SerranoTests/op/basic/binary_op/mult_op_test.swift @@ -0,0 +1,51 @@ +// +// mult_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class MultOpDelegate: OperatorDelegateConvBinaryOp { + + required public convenience init(compareBlock: (([Tensor], Tensor) -> Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/binary_op/pow_op_test.swift b/Tests/SerranoTests/op/basic/binary_op/pow_op_test.swift new file mode 100644 index 0000000..ea4fef0 --- /dev/null +++ b/Tests/SerranoTests/op/basic/binary_op/pow_op_test.swift @@ -0,0 +1,63 @@ +// +// pow_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Accelerate +@testable import Serrano + +class PowOpDelegate: OperatorDelegateConvBinaryOp { + + required public convenience init(compareBlock: (([Tensor], Tensor) -> Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + for i in 0..() + testCase.testAll() + } + +// func testPof() { +// let a = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: [2, 3])) +// let b = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: [2, 3])) +// let c = randomTensor(fromShape: TensorShape(dataType: .float, shape: [2, 3])) +// print(a.flatArrayFloat()) +// print(b.flatArrayFloat()) +// print("==") +// var count = Int32(c.count) +// vvpowf(c.contentsAddress, b.contentsAddress, a.contentsAddress, &count) +// print(a.flatArrayFloat()) +// print(b.flatArrayFloat()) +// print(c.flatArrayFloat()) +// +// print(powf(0, 0)) +// } +} diff --git a/Tests/SerranoTests/op/basic/binary_op/rdiv_op_test.swift b/Tests/SerranoTests/op/basic/binary_op/rdiv_op_test.swift new file mode 100644 index 0000000..78e36f4 --- /dev/null +++ b/Tests/SerranoTests/op/basic/binary_op/rdiv_op_test.swift @@ -0,0 +1,51 @@ +// +// rdiv_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class RDivOpDelegate: OperatorDelegateConvBinaryOp { + + required public convenience init(compareBlock: (([Tensor], Tensor) -> Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/binary_op/sub_op_test.swift b/Tests/SerranoTests/op/basic/binary_op/sub_op_test.swift new file mode 100644 index 0000000..3ebc9db --- /dev/null +++ b/Tests/SerranoTests/op/basic/binary_op/sub_op_test.swift @@ -0,0 +1,49 @@ +// +// sub_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class SubOpDelegate: OperatorDelegateConvBinaryOp { + + required public convenience init(compareBlock: (([Tensor], Tensor) -> Void)?) { + let blcok = {(inputTensors: [Tensor], resultTensor: Tensor) -> Void in + let inputReaderA = inputTensors[0].floatValueReader + let inputReaderB = inputTensors[1].floatValueReader + let resultReader = resultTensor.floatValueReader + + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/broadcast/broadcast_add_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/broadcast_add_op_test.swift new file mode 100644 index 0000000..44dd664 --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/broadcast_add_op_test.swift @@ -0,0 +1,48 @@ +// +// broadcast_add_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class OperatorDelegateConvBroadcastAddOp: OperatorDelegateConvBroadcastArithmeticOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)? = nil) { + let blcok = {(veryfyTensors: [Tensor], resultTensors: [Tensor]) -> Void in + let readerA = veryfyTensors[0].floatValueReader + let readerB = veryfyTensors[1].floatValueReader + let resultReader = resultTensors[0].floatValueReader + XCTAssertEqual(veryfyTensors[0].count, resultTensors[0].count) + XCTAssertEqual(veryfyTensors[1].count, resultTensors[0].count) + for i in 0..() + testCase.testAll() + + } + +} diff --git a/Tests/SerranoTests/op/basic/broadcast/broadcast_arithmetic_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/broadcast_arithmetic_op_test.swift new file mode 100644 index 0000000..2e00450 --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/broadcast_arithmetic_op_test.swift @@ -0,0 +1,303 @@ +// +// broadcast_arithmetic_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Dispatch +@testable import Serrano + + +fileprivate func generateShapes(targetShape: TensorShape, valid: Bool) -> [TensorShape] { + let targetShapeReversed = Array(targetShape.shapeArray.reversed()) + var shapesReversed = [[Int]]() + + if valid { + //valid + for _ in 0..<2 { + var newShapeRevsered = Array(targetShapeReversed) + + if randomInt([100, 10000000]) % 3 == 0 { // remove last dim + for _ in 0.. Void + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)? = nil) { + let blcok = {(veryfyTensors: [Tensor], resultTensors: [Tensor]) -> Void in + print("OVERRIDE") + } + self.init(block: blcok) + } + + public init(block: @escaping ([Tensor], [Tensor]) -> Void) { + self.compareBlock = block + super.init() + } + + override public func compare() { + // do broadcasting + var tensorA = self.veryfyTensors[0] + var tensorB = self.veryfyTensors[1] + if tensorA.shape != tensorB.shape { + let broadcastOp = BroadcastOperator(targetShape: max(tensorA.shape, tensorB.shape)) + if tensorA.shape < tensorB.shape { + // broadcast A + broadcastOp.inputTensors = [ self.veryfyTensors[0]] + tensorA = SerranoResourceManager.globalManager.allocateTensor(tensorB.shape) + broadcastOp.outputTensors = [tensorA] + } else if tensorA.shape > tensorB.shape { + // broadcast B + broadcastOp.inputTensors = [ self.veryfyTensors[1]] + tensorB = SerranoResourceManager.globalManager.allocateTensor(tensorA.shape) + broadcastOp.outputTensors = [tensorB] + } + broadcastOp.compute(.CPU) + } + + self.compareBlock([tensorA, tensorB], self.resultTensors) + } +} + +class BroadcastArithmeticOpTest: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testAll() { + self.testInit() + self.testOutputShape() + self.testInputOutputTensorsCheck() + self.testCompute() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target + init + */ + func testInit() { + let numCase = 100 + for i in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + let op = Op() + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 100 + let op = Op() + for i in 0..= 8 { + shapeA = randomShape(dimensions: 2, dimensionSizeRange: [1000, 1500], dataType: .float) + } + let shapeB = generateShapes(targetShape: shapeA, valid: true).first! + if i % 3 == 0 { + inputTensors.append(randomTensor(fromShape: shapeA)) + inputTensors.append(randomTensor(fromShape: shapeB)) + } else { + inputTensors.append(randomTensor(fromShape: shapeB)) + inputTensors.append(randomTensor(fromShape: shapeA)) + } + print("Input tensor A: \(inputTensors[0].description)") + print("Input tensor B: \(inputTensors[1].description)") + + // output tensor + let maxShape = max(inputTensors[0].shape, inputTensors[1].shape) + outputTensors.append(randomTensor(fromShape: maxShape)) + print("Output tensor: \(outputTensors.last!.description)") + + op.inputTensors = inputTensors + op.outputTensors = outputTensors + delegate.veryfyTensors = inputTensors + + if i % 2 == 0 { + print("Run on CPU") + workingGroup.enter() + op.computeAsync( .CPU) + } else { + print("Run on GPU") + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + print("No gpu available, give up Test \(i+1)\n\n\n)") + continue + } + workingGroup.enter() + op.computeAsync( .GPU) + } + workingGroup.wait() + + SerranoResourceManager.globalManager.releaseAllResources() + print("Finish Test case \(i+1)\n\n") + } + + } +} diff --git a/Tests/SerranoTests/op/basic/broadcast/broadcast_div_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/broadcast_div_op_test.swift new file mode 100644 index 0000000..3e6a20f --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/broadcast_div_op_test.swift @@ -0,0 +1,48 @@ +// +// broadcast_div_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class OperatorDelegateConvBroadcastDivOp: OperatorDelegateConvBroadcastArithmeticOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(veryfyTensors: [Tensor], resultTensors: [Tensor]) -> Void in + let readerA = veryfyTensors[0].floatValueReader + let readerB = veryfyTensors[1].floatValueReader + let resultReader = resultTensors[0].floatValueReader + XCTAssertEqual(veryfyTensors[0].count, resultTensors[0].count) + XCTAssertEqual(veryfyTensors[1].count, resultTensors[0].count) + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/broadcast/broadcast_mult_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/broadcast_mult_op_test.swift new file mode 100644 index 0000000..da733dd --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/broadcast_mult_op_test.swift @@ -0,0 +1,47 @@ +// +// broadcast_mult_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class OperatorDelegateConvBroadcastMultOp: OperatorDelegateConvBroadcastArithmeticOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(veryfyTensors: [Tensor], resultTensors: [Tensor]) -> Void in + let readerA = veryfyTensors[0].floatValueReader + let readerB = veryfyTensors[1].floatValueReader + let resultReader = resultTensors[0].floatValueReader + XCTAssertEqual(veryfyTensors[0].count, resultTensors[0].count) + XCTAssertEqual(veryfyTensors[1].count, resultTensors[0].count) + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/broadcast/broadcast_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/broadcast_op_test.swift new file mode 100644 index 0000000..fba8d16 --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/broadcast_op_test.swift @@ -0,0 +1,282 @@ +// +// broad_cast_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/13/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +fileprivate func verfyShape(targetShape: TensorShape, result: [TensorShape]) -> Bool { + for shape in result { + if !shape.shapeArray.elementsEqual(targetShape.shapeArray) { + return false + } + } + return true +} + + +fileprivate func generateShapes(targetShape: TensorShape, valid: Bool) -> [TensorShape] { + let targetShapeReversed = Array(targetShape.shapeArray.reversed()) + var shapesReversed = [[Int]]() + + if valid { + //valid + for _ in 0.. (targetShape: TensorShape, shapes: [TensorShape], valid: Bool) { + + // generate target shape + let targetShape = randomShape(dimensions: randomInt([1, 5]), dimensionSizeRange: [1, 5], dataType: .float) + let targetShapeReversed = Array(targetShape.shapeArray.reversed()) + + var valid = true + if randomInt([100, 10000000]) % 3 == 0 { valid = false } + + var shapesReversed = [[Int]]() + if valid { + //valid + for _ in 0.. [TensorShape]? + */ + func testOutputShapes() { + let numCase = 100 + + for i in 0.. (check: Bool, msg: String) + */ + func testCheck() { + let numCase = 100 + + for i in 0.. Void)?) { + let blcok = {(veryfyTensors: [Tensor], resultTensors: [Tensor]) -> Void in + let readerA = veryfyTensors[0].floatValueReader + let readerB = veryfyTensors[1].floatValueReader + let resultReader = resultTensors[0].floatValueReader + XCTAssertEqual(veryfyTensors[0].count, resultTensors[0].count) + XCTAssertEqual(veryfyTensors[1].count, resultTensors[0].count) + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/broadcast/pad_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/pad_op_test.swift new file mode 100644 index 0000000..a050bc2 --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/pad_op_test.swift @@ -0,0 +1,35 @@ +// +// pad_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/18/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +class pad_op_test: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/Tests/SerranoTests/op/basic/broadcast/transpose_op_test.swift b/Tests/SerranoTests/op/basic/broadcast/transpose_op_test.swift new file mode 100644 index 0000000..9463508 --- /dev/null +++ b/Tests/SerranoTests/op/basic/broadcast/transpose_op_test.swift @@ -0,0 +1,285 @@ +// +// transpose_op.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +public class OperatorDelegateConvTransposeOperator: OperatorDelegateConv { + + override public func compare() { + XCTAssertTrue(self.resultTensors.count == self.veryfyTensors.count) + + for tensorIndex in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + let op = TransposeOperator() + for i in 0.. (check: Bool, msg: String) + */ + func testInputOuputTensorsCheck() { + let numCase = 100 + let op = TransposeOperator() + for i in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputCheck() { + let numCase = 100 + for i in 0..= 18 { + dimRange = [1000, 1200] + } + + // generate valid + var shapeA = randomShape(dimensions: 2, dimensionSizeRange: dimRange, dataType: .int) + var shapeB = TensorShape(dataType: .int, shape: [shapeA.shapeArray[1], randomInt(dimRange)]) + + // transpose + if randomInt([0, 10]) % 2 == 0 { + transposeA = true + shapeA = shapeA.transposed() + } + if randomInt([0, 10]) % 2 == 0 { + transposeB = true + shapeB = shapeB.transposed() + } + inputTensors.append(randomTensor(fromShape: shapeA)) + print("Input A: \(shapeA), transpose: \(transposeA)") + + inputTensors.append(randomTensor(fromShape: shapeB)) + print("Input B: \(shapeB), transpose: \(transposeB)") + + var AShapeArray = shapeA.shapeArray + if transposeA { + AShapeArray = [AShapeArray[1], AShapeArray[0]] + } + var BShapeArray = shapeB.shapeArray + if transposeB { + BShapeArray = [BShapeArray[1], BShapeArray[0]] + } + + let outTensor = randomTensor(fromShape: TensorShape(dataType: .int, shape: [AShapeArray[0], BShapeArray[1]])) + outputTensors.append(outTensor) + print("Output C: \(outTensor.shape)") + + op.transposeA = transposeA + op.transposeB = transposeB + op.inputTensors = inputTensors + op.outputTensors = outputTensors + delegate.veryfyTensors = inputTensors + delegate.transposeA = transposeA + delegate.transposeB = transposeB + + if i % 2 == 0 { + print("Run CPU") + workGroup.enter() + op.computeAsync(.CPU) + } else { + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + print("No available GPU, give up test.\n\n") + continue + } + workGroup.enter() + op.computeAsync(.GPU) + } + + workGroup.wait() + + SerranoResourceManager.globalManager.releaseAllResources() + print("Finish Test \(i+1)\n\n") + } + } + + func testKernelPerformanceSingle() { + + let op = MatrixMultOperator() + + // gpu initial + _ = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU) + + + + var outputTensors = [Tensor]() + var inputTensors = [Tensor]() + + // generate tensors + let dimRange: [Int] = [1200, 1200] + + // generate valid + let shapeA = randomShape(dimensions: 2, dimensionSizeRange: dimRange, dataType: .int) + let shapeB = TensorShape(dataType: .int, shape: [shapeA.shapeArray[1], randomInt(dimRange)]) + + + inputTensors.append(randomTensor(fromShape: shapeA)) + print("Input A: \(shapeA)") + + inputTensors.append(randomTensor(fromShape: shapeB)) + print("Input B: \(shapeB)") + + let AShapeArray = shapeA.shapeArray + let BShapeArray = shapeB.shapeArray + + let outTensor = randomTensor(fromShape: TensorShape(dataType: .int, shape: [AShapeArray[0], BShapeArray[1]])) + outputTensors.append(outTensor) + print("Output C: \(outTensor.shape)") + + op.inputTensors = inputTensors + op.outputTensors = outputTensors + + op.kernel = MatrixMultKernel.Single + self.measure { + op.compute(.GPU) + } + } + + func testKernelPerformanceSubMatrix() { + + let op = MatrixMultOperator() + + // gpu initial + _ = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU) + + + + var outputTensors = [Tensor]() + var inputTensors = [Tensor]() + + // generate tensors + let dimRange: [Int] = [1200, 1200] + + // generate valid + let shapeA = randomShape(dimensions: 2, dimensionSizeRange: dimRange, dataType: .int) + let shapeB = TensorShape(dataType: .int, shape: [shapeA.shapeArray[1], randomInt(dimRange)]) + + + inputTensors.append(randomTensor(fromShape: shapeA)) + print("Input A: \(shapeA)") + + inputTensors.append(randomTensor(fromShape: shapeB)) + print("Input B: \(shapeB)") + + let AShapeArray = shapeA.shapeArray + let BShapeArray = shapeB.shapeArray + + let outTensor = randomTensor(fromShape: TensorShape(dataType: .int, shape: [AShapeArray[0], BShapeArray[1]])) + outputTensors.append(outTensor) + print("Output C: \(outTensor.shape)") + + op.inputTensors = inputTensors + op.outputTensors = outputTensors + + op.kernel = MatrixMultKernel.SubMatrix + self.measure { + op.compute(.GPU) + } + } +} diff --git a/Tests/SerranoTests/op/basic/reduce/reduce_max_op_test.swift b/Tests/SerranoTests/op/basic/reduce/reduce_max_op_test.swift new file mode 100644 index 0000000..0ed3a08 --- /dev/null +++ b/Tests/SerranoTests/op/basic/reduce/reduce_max_op_test.swift @@ -0,0 +1,29 @@ +// +// reduce_max_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/1/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + + +class ReduceMaxOpDelegate: OperatorDelegateConvReduceOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + + } + self.init(block: blcok) + } +} + +class ReduceMaxOpTest: XCTestCase { + + func test() { + let testCase = ReduceOpTest() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/reduce/reduce_mean_op_test.swift b/Tests/SerranoTests/op/basic/reduce/reduce_mean_op_test.swift new file mode 100644 index 0000000..5b3ea43 --- /dev/null +++ b/Tests/SerranoTests/op/basic/reduce/reduce_mean_op_test.swift @@ -0,0 +1,29 @@ +// +// reduce_mean_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/2/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + + +class ReduceMeanOpDelegate: OperatorDelegateConvReduceOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + + } + self.init(block: blcok) + } +} + +class ReduceMeanOpTest: XCTestCase { + + func test() { + let testCase = ReduceOpTest() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/reduce/reduce_min_op_test.swift b/Tests/SerranoTests/op/basic/reduce/reduce_min_op_test.swift new file mode 100644 index 0000000..786ef70 --- /dev/null +++ b/Tests/SerranoTests/op/basic/reduce/reduce_min_op_test.swift @@ -0,0 +1,29 @@ +// +// reduce_min_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/1/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + + +class ReduceMinOpDelegate: OperatorDelegateConvReduceOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + + } + self.init(block: blcok) + } +} + +class ReduceMinOpTest: XCTestCase { + + func test() { + let testCase = ReduceOpTest() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/reduce/reduce_op_test.swift b/Tests/SerranoTests/op/basic/reduce/reduce_op_test.swift new file mode 100644 index 0000000..55db232 --- /dev/null +++ b/Tests/SerranoTests/op/basic/reduce/reduce_op_test.swift @@ -0,0 +1,289 @@ +// +// reduce_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +public class OperatorDelegateConvReduceOp: OperatorDelegateConv { + + public var compareBlock: (Tensor, Tensor) -> Void + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + print() + } + self.init(block: blcok) + } + + // override this func + public init(block: @escaping (Tensor, Tensor) -> Void) { + self.compareBlock = block + super.init() + } + + override public func compare() { + XCTAssertTrue(self.resultTensors.first!.count == self.veryfyTensors.first!.count) + + for i in 0..: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + + func testAll() { + print(String(repeating: "=", count: 80) + "\n\n") + self.testOutputShape() + + print(String(repeating: "=", count: 80) + "\n\n") + self.testInputOutputTensorCheck() + + print(String(repeating: "=", count: 80) + "\n\n") + self.testCompute() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Test init functions + */ + func testInit() { + let numCase = 100 + let op = ReduceOp(axis: [Int]()) + for _ in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + let op = ReduceOp(axis: [Int]()) + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorCheck() { + let numCase = 100 + let op = ReduceOp(axis: [Int]()) + for i in 0.. Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + + } + self.init(block: blcok) + } +} + +class ReduceProductOpTest: XCTestCase { + + func test() { + let testCase = ReduceOpTest() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/reduce/reduce_sum_op_test.swift b/Tests/SerranoTests/op/basic/reduce/reduce_sum_op_test.swift new file mode 100644 index 0000000..1f4ec68 --- /dev/null +++ b/Tests/SerranoTests/op/basic/reduce/reduce_sum_op_test.swift @@ -0,0 +1,29 @@ +// +// reduce_sum_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/28/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + + +class ReduceSumOpDelegate: OperatorDelegateConvReduceOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + + } + self.init(block: blcok) + } +} + +class ReduceSumOpTest: XCTestCase { + + func test() { + let testCase = ReduceOpTest() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/abs_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/abs_op_test.swift new file mode 100644 index 0000000..1b3b09a --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/abs_op_test.swift @@ -0,0 +1,48 @@ +// +// abs_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class AbsOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arccos_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arccos_op_test.swift new file mode 100644 index 0000000..c3a1ec4 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arccos_op_test.swift @@ -0,0 +1,48 @@ +// +// arccos_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// +import XCTest +@testable import Serrano + +class ArccosOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arccosh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arccosh_op_test.swift new file mode 100644 index 0000000..6575502 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arccosh_op_test.swift @@ -0,0 +1,49 @@ +// +// arccosh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class ArccoshOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arcsin_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arcsin_op_test.swift new file mode 100644 index 0000000..e3dc5e3 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arcsin_op_test.swift @@ -0,0 +1,48 @@ +// +// arcsin_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// +import XCTest +@testable import Serrano + +class ArcsinOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arcsinh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arcsinh_op_test.swift new file mode 100644 index 0000000..86e2f7d --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arcsinh_op_test.swift @@ -0,0 +1,49 @@ +// +// arcsinh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class ArcsinhOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arctan_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arctan_op_test.swift new file mode 100644 index 0000000..0e6a051 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arctan_op_test.swift @@ -0,0 +1,49 @@ +// +// arctan_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class ArctanOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/arctanh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/arctanh_op_test.swift new file mode 100644 index 0000000..458bde7 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/arctanh_op_test.swift @@ -0,0 +1,50 @@ +// +// arctanh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class ArctanhOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/ceil_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/ceil_op_test.swift new file mode 100644 index 0000000..5700a4d --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/ceil_op_test.swift @@ -0,0 +1,49 @@ +// +// ceil_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class CeilOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/copy_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/copy_op_test.swift new file mode 100644 index 0000000..d537d15 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/copy_op_test.swift @@ -0,0 +1,101 @@ +// +// copy_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 4/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Dispatch +@testable import Serrano + + +/** + Covenient class for testing + */ +class OperatorDelegateConvCopy: OperatorDelegateConv { + + override public func compare() { + XCTAssertTrue(self.resultTensors.count == self.veryfyTensors.count) + + for i in 0.. [Tensor] + func compute(asyncWithInputTensors tensors: [Tensor], computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) + */ + func testCompute() { + let numCase = 100 + for i in 0.. Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/cosh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/cosh_op_test.swift new file mode 100644 index 0000000..11e239b --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/cosh_op_test.swift @@ -0,0 +1,49 @@ +// +// cosh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class CoshOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/degree_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/degree_op_test.swift new file mode 100644 index 0000000..e138165 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/degree_op_test.swift @@ -0,0 +1,49 @@ +// +// degree_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class DegreeOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/exp_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/exp_op_test.swift new file mode 100644 index 0000000..fe6ce8e --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/exp_op_test.swift @@ -0,0 +1,49 @@ +// +// exp_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class ExpOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/expm1_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/expm1_op_test.swift new file mode 100644 index 0000000..d740120 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/expm1_op_test.swift @@ -0,0 +1,54 @@ +// +// expm1_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class Expm1OpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + + diff --git a/Tests/SerranoTests/op/basic/unary_op/floor_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/floor_op_test.swift new file mode 100644 index 0000000..04be3d4 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/floor_op_test.swift @@ -0,0 +1,48 @@ +// +// floor_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class FloorOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/log10_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/log10_op_test.swift new file mode 100644 index 0000000..e597f4d --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/log10_op_test.swift @@ -0,0 +1,52 @@ +// +// log10_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class Log10OpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/log1p_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/log1p_op_test.swift new file mode 100644 index 0000000..dc0bd5b --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/log1p_op_test.swift @@ -0,0 +1,53 @@ +// +// log1p_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class Log1pOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/log2_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/log2_op_test.swift new file mode 100644 index 0000000..cca47c8 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/log2_op_test.swift @@ -0,0 +1,52 @@ +// +// log2_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class Log2OpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/log_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/log_op_test.swift new file mode 100644 index 0000000..8d263d5 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/log_op_test.swift @@ -0,0 +1,53 @@ +// +// log_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class LogOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/radian_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/radian_op_test.swift new file mode 100644 index 0000000..284bd46 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/radian_op_test.swift @@ -0,0 +1,49 @@ +// +// radian_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class RadianOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/rint_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/rint_op_test.swift new file mode 100644 index 0000000..ccd8e93 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/rint_op_test.swift @@ -0,0 +1,51 @@ +// +// rint_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class RintOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/round_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/round_op_test.swift new file mode 100644 index 0000000..d9a037a --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/round_op_test.swift @@ -0,0 +1,48 @@ +// +// round_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class RoundOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/rsqrt_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/rsqrt_op_test.swift new file mode 100644 index 0000000..08de0a3 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/rsqrt_op_test.swift @@ -0,0 +1,54 @@ +// +// rsqrt_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class RsqrtOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/sin_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/sin_op_test.swift new file mode 100644 index 0000000..9b6ccfa --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/sin_op_test.swift @@ -0,0 +1,50 @@ +// +// sin_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class SinOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/basic/unary_op/sinh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/sinh_op_test.swift new file mode 100644 index 0000000..2908a3e --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/sinh_op_test.swift @@ -0,0 +1,50 @@ +// +// sinh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class SinhOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/sqrt_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/sqrt_op_test.swift new file mode 100644 index 0000000..47632c9 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/sqrt_op_test.swift @@ -0,0 +1,52 @@ +// +// sqrt_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class SqrtOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/square_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/square_op_test.swift new file mode 100644 index 0000000..8470cb8 --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/square_op_test.swift @@ -0,0 +1,53 @@ +// +// square_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest + +import XCTest +@testable import Serrano + +class SquareOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/tan_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/tan_op_test.swift new file mode 100644 index 0000000..7ce01be --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/tan_op_test.swift @@ -0,0 +1,55 @@ +// +// tan_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class TanOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/basic/unary_op/tanh_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/tanh_op_test.swift new file mode 100644 index 0000000..2f52f4f --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/tanh_op_test.swift @@ -0,0 +1,49 @@ +// +// tanh_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/6/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// +import XCTest +@testable import Serrano + +class TanhOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} + diff --git a/Tests/SerranoTests/op/basic/unary_op/unary_op_test.swift b/Tests/SerranoTests/op/basic/unary_op/unary_op_test.swift new file mode 100644 index 0000000..2520b0d --- /dev/null +++ b/Tests/SerranoTests/op/basic/unary_op/unary_op_test.swift @@ -0,0 +1,249 @@ +// +// unary_op_test.swift +// SerranoTests +// +// Created by ZHONGHAO LIU on 6/5/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Dispatch +import Metal +@testable import Serrano + + +public class OperatorDelegateConvUnaryOp: OperatorDelegateConv { + + public var compareBlock: (Tensor, Tensor) -> Void + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + print() + } + self.init(block: blcok) + } + + // override this func + public init(block: @escaping (Tensor, Tensor) -> Void) { + self.compareBlock = block + super.init() + } + + override public func compare() { + XCTAssertTrue(self.resultTensors.first!.count == self.veryfyTensors.first!.count) + + for i in 0..: XCTestCase { + + override public func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + + } + + override public func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + +// func testExample() { +// // This is an example of a functional test case. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// } +// +// func testPerformanceExample() { +// // This is an example of a performance test case. +// self.measure { +// // Put the code you want to measure the time of here. +// } +// } + + public func testAll() { + self.testInit() + self.testOuputShapesCheck() + self.testInputOutputTensorsCheck() + self.testCompute() + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Test init functions + */ + func testInit() { + let numCase = 100 + let op = UnaryOp() + for _ in 0.. [TensorShape]? + */ + func testOuputShapesCheck() { + let numCase = 100 + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 50 + let op = UnaryOp() + for i in 0.. [Tensor] + func compute(asyncWithInputTensors tensors:[Tensor], computationMode: OperatorComputationMode = SerranoEngine.configuredEngine.defaultComputationMode) + */ + + func testCompute() { + let caseNum = 10 + let op = UnaryOp() + + // configure engine + let (_, msg) = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + + // setup delegate + let delegate = OpDelegate(compareBlock: nil) + let workingGroup = DispatchGroup() + delegate.dispatchGroup = workingGroup + op.computationDelegate = delegate + + + for i in 0.. Void)?) { + let alpha: Float = 1.0 + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/leakyrelu_op_test.swift b/Tests/SerranoTests/op/nn/activation/leakyrelu_op_test.swift new file mode 100644 index 0000000..d5285f6 --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/leakyrelu_op_test.swift @@ -0,0 +1,51 @@ +// +// leakyrLeakyReLU_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/9/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class LeakyReLUOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let alpha: Float = 0.3 + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/linear_op_test.swift b/Tests/SerranoTests/op/nn/activation/linear_op_test.swift new file mode 100644 index 0000000..f8dd8ef --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/linear_op_test.swift @@ -0,0 +1,49 @@ +// +// linear_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class LinearOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/prelu_op_test.swift b/Tests/SerranoTests/op/nn/activation/prelu_op_test.swift new file mode 100644 index 0000000..b86711c --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/prelu_op_test.swift @@ -0,0 +1,217 @@ +//// +//// PReLU_op_test.swift +//// serrano +//// +//// Created by ZHONGHAO LIU on 7/10/17. +//// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +//// +// +//import XCTest +//@testable import Serrano +// +//class PReLUOpDelegate: OperatorDelegateConvUnaryOp { +// +// public var alpha: [Tensor] = [Tensor]() +// +// required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)? = nil) { +// let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in +// print("NOT USE") +// } +// self.init(block: blcok) +// } +// +// public convenience init(alpha: [Tensor]) { +// self.init(compareBlock: nil) +// self.alpha = alpha +// } +// +// override public func compare() { +// XCTAssertEqual(self.alpha.count, self.resultTensors.count) +// XCTAssertEqual(self.veryfyTensors.count, self.resultTensors.count) +// for tensorIndex in 0..() +// testCase.testInit() +// testCase.testOuputShapesCheck() +// } +// +// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// /** +// Target: +// public override func inputOutputTensorsCheck() -> (check: Bool, msg: String) +// */ +// func testInputOutputTensorsCheck() { +// let numCase = 100 +// let op = PReLUOperator() +// for i in 0.. Void)? = nil) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + print("NOT USE") + } + self.init(block: blcok) + } + + override public func compare() { + XCTAssertTrue(self.resultTensors.first!.count == self.veryfyTensors.first!.count) + + for i in 0..() + testCase.testInit() + testCase.testOuputShapesCheck() + testCase.testInputOutputTensorsCheck() + } + + func testAlpha() { + let numCase = 10 + let op = ReLUOperator() + + let delegate = ReLUOpDelegate() + let workingGroup = DispatchGroup() + delegate.dispatchGroup = workingGroup + op.computationDelegate = delegate + + // configure engine + let (_, msg) = SerranoEngine.configuredEngine.configureEngine(computationMode: .GPU, serranoCommandQueue: nil, serranoMTLLibrary: nil, systemDefaultGPUDevice: nil) + + for i in 0.. Void)?) { + let alpha: Float = 1.673263 + let scale: Float = 1.050701 + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/sigmoid_op_test.swift b/Tests/SerranoTests/op/nn/activation/sigmoid_op_test.swift new file mode 100644 index 0000000..bd4e283 --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/sigmoid_op_test.swift @@ -0,0 +1,49 @@ +// +// sigmoid_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class SigmoidOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/softmax_op_test.swift b/Tests/SerranoTests/op/nn/activation/softmax_op_test.swift new file mode 100644 index 0000000..db681df --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/softmax_op_test.swift @@ -0,0 +1,204 @@ +// +// softmax_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/2/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Dispatch +import Metal +@testable import Serrano + + +import XCTest +@testable import Serrano + +class SoftmaxOpDelegate: OperatorDelegateConvUnaryOp { + + public var dim: Int = -1 + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + print() + } + self.init(block: blcok) + } + + public override func compare() { + XCTAssertTrue(self.resultTensors.first!.count == self.veryfyTensors.first!.count) + + for tensorIndex in 0..() + testCase.testAll() + } +} + +class SoftmaxOverrideOpTest: UnarOpTest { + /// Override output shapce check + override func testOuputShapesCheck() { + let numCase = 100 + let op = SoftmaxOp() + for i in 0.. Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/nn/activation/softsign_op_test.swift b/Tests/SerranoTests/op/nn/activation/softsign_op_test.swift new file mode 100644 index 0000000..78a1cca --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/softsign_op_test.swift @@ -0,0 +1,49 @@ +// +// softsign_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 6/27/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class SoftsignOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/activation/thresholdedrelu_op_test.swift b/Tests/SerranoTests/op/nn/activation/thresholdedrelu_op_test.swift new file mode 100644 index 0000000..d46784c --- /dev/null +++ b/Tests/SerranoTests/op/nn/activation/thresholdedrelu_op_test.swift @@ -0,0 +1,50 @@ +// +// thresholdedReLU_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/9/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// +import XCTest +@testable import Serrano + +class ThresholdedReLUOpDelegate: OperatorDelegateConvUnaryOp { + + required public convenience init(compareBlock: ((Tensor, Tensor) -> Void)?) { + let alpha: Float = 1.0 + let blcok = {(rawTensor: Tensor, resultTensor: Tensor) -> Void in + XCTAssertEqual(rawTensor.count, resultTensor.count) + let readerReader = rawTensor.floatValueReader + let resultReader = resultTensor.floatValueReader + for i in 0..() + testCase.testAll() + } +} diff --git a/Tests/SerranoTests/op/nn/common/fullyconnected_op_test.swift b/Tests/SerranoTests/op/nn/common/fullyconnected_op_test.swift new file mode 100644 index 0000000..03dafd5 --- /dev/null +++ b/Tests/SerranoTests/op/nn/common/fullyconnected_op_test.swift @@ -0,0 +1,384 @@ +// +// fullyconnected_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano +import Dispatch + +import Accelerate + +public class OperatorDelegateConvFullyConnctOp: OperatorDelegateConv { + + override public func compare() { + + // Compare resutl + let inputTensor = self.veryfyTensors[0] + let weightTensor = self.veryfyTensors[1] + let biasTensor = self.veryfyTensors[2] + let tensorC = self.resultTensors[0] + + + let readA = inputTensor.contentsAddress + let readB = weightTensor.contentsAddress + let verifyTensor = SerranoResourceManager.globalManager.allocateTensors( [tensorC.shape]).first! + let verifyAddres = verifyTensor.contentsAddress + let M = Int32(1) + let N = Int32(weightTensor.shape.shapeArray[1]) + let K = Int32(inputTensor.count) + cblas_sgemm(CblasRowMajor, cblasTrans(false), cblasTrans(false), M, N, K, + 1.0, readA, K, readB, N, 0.0, verifyAddres, N) + // plus bias + vDSP_vadd(verifyAddres, 1, biasTensor.contentsAddress, 1, verifyAddres, 1, vDSP_Length(verifyTensor.count)) + + + let verifyReader = verifyTensor.floatValueReader + let readC = tensorC.floatValueReader + for i in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + let op = FullyconnectedOperator(inputDim:1, numUnits: 2) + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 100 + let op = FullyconnectedOperator(inputDim:1, numUnits: 1) + + var numUnits = 0 + var inputDim = 0 + var weight:Tensor? + var bias: Tensor? + var inputTensors: [Tensor]? + var outputTensors: [Tensor]? + + + for i in 0.. Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(outputShape) + let (channel, inHeight, inWidth) = parseImgChannelShapeInfo(channelOrder, shapeArray: input.shape.shapeArray) + let numFilter = weight.shape.shapeArray[0] + + // out bounary + let outHeight = kernelScanningOutSize(pad, inputSize: inHeight, kernelSize: kernelSize[0], stride: stride[0]) + let outWidth = kernelScanningOutSize(pad, inputSize: inWidth, kernelSize: kernelSize[1], stride: stride[1]) + + // calculate + for h in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 100 + for i in 0..= 8 { + range = [800, 800] + } + let channel = randomInt([1, 4]) + var inputShape = TensorShape(dataType: .int, shape: [randomInt(range), randomInt(range), channel]) + if channelOrder == TensorChannelOrder.First { + inputShape = TensorShape(dataType: .int, shape: [channel, randomInt(range), randomInt(range)]) + } + + // conv op + let convOp = ConvOperator2D(numFilters: numFilters, kernelSize: kernelSize, stride: stride, + padMode: padding, channelPosition: channelOrder, dilation: diliation) + convOp.computationDelegate = delegate + + // generate input tensors + var input = [Tensor]() + input.append(randomTensor(fromShape: inputShape)) + + + // generate output tensors + let outputShape = convOp.outputShape(shapeArray: input.map {$0.shape}) + var output = [Tensor]() + for shape in outputShape! { + output.append(Tensor(repeatingValue: 0.0, tensorShape: shape)) + } + + // weight + let weight:Tensor = randomTensor(fromShape: TensorShape(dataType: .int, + shape: [numFilters, channel, kernelSize[0], kernelSize[1]])) + + convOp.inputTensors = input + convOp.outputTensors = output + convOp.weight = weight + delegate.op = convOp + + if i % 2 == 0 { + workGroup.enter() + print("Run on CPU") + delegate.startTime = CFAbsoluteTimeGetCurrent() + convOp.computeAsync(.CPU) + } else { + if !SerranoEngine.configuredEngine.hasAvailableGPU() { + print("No GPU available. Give up test.\n") + continue + } + workGroup.enter() + print("Run on GPU") + delegate.startTime = CFAbsoluteTimeGetCurrent() + convOp.computeAsync(.GPU) + } + + workGroup.wait() + + print("Finish test case \(i+1)\n") + + } + } + +} diff --git a/Tests/SerranoTests/op/nn/conv/img2col_op_test.swift b/Tests/SerranoTests/op/nn/conv/img2col_op_test.swift new file mode 100644 index 0000000..d6a1dd5 --- /dev/null +++ b/Tests/SerranoTests/op/nn/conv/img2col_op_test.swift @@ -0,0 +1,464 @@ +// +// img2col_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 8/16/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +class OperatorDelegateConvImg2Col: OperatorDelegateConv { + + public var op: Img2ColOperator? = nil + + override public func compare() { + XCTAssertNotNil(self.op) + + let inputTensors = self.op!.inputTensors! + let patchHeight = self.op!.patchSize[0] + let patchWidth = self.op!.patchSize[1] + let strideHeight = self.op!.stride[0] + let strideWidth = self.op!.stride[1] + let patchElementCount = patchWidth * patchHeight + + // compute validation tensors + var checkOutputTensors = [Tensor]() + for inTensor in inputTensors { + let outShape = self.op!.outputShape(shapeArray: [inTensor.shape])!.first! + let checkTensor = Tensor(repeatingValue: -1.0, tensorShape: outShape) + + let inShapeArray = inTensor.shape.shapeArray + let (channels, inHeight, inWidth) = parseImgChannelShapeInfo(self.op!.channelPosition, shapeArray: inShapeArray) + + // get out dim size + let outHeight = kernelScanningOutSize(self.op!.padMode, inputSize: inHeight, kernelSize: patchHeight, stride: strideHeight) + let outWidth = kernelScanningOutSize(self.op!.padMode, inputSize: inWidth, kernelSize: patchWidth, stride: strideWidth) + + for i in 0.. [Int] { + if same { + let size = randomInt(range) + return [size, size] + } else { + return [randomInt(range), randomInt(range)] + } + } + + func makeValidStride(_ range: [Int], same: Bool = true) -> [Int] { + if same { + let size = randomInt(range) + return [size, size] + } else { + return [randomInt(range), randomInt(range)] + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + init + */ + func testInit() { + let numCase = 100 + + for i in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 1 + + for i in 0.. Tensor { + let inputShapeArray = input.shape.shapeArray + let (channel, inHeight, inWidth) = parseImgChannelShapeInfo(order, shapeArray: inputShapeArray) + var outShapeArray = [channel, + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1])] + if order == TensorChannelOrder.Last { + outShapeArray = [ + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1]), + channel] + } + let tensor = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: outShapeArray)) + let (_, outHeight, outWidth) = parseImgChannelShapeInfo(order, shapeArray: outShapeArray) + + for c in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/nn/pooling/maxpool2d_op_test.swift b/Tests/SerranoTests/op/nn/pooling/maxpool2d_op_test.swift new file mode 100644 index 0000000..a167c4c --- /dev/null +++ b/Tests/SerranoTests/op/nn/pooling/maxpool2d_op_test.swift @@ -0,0 +1,100 @@ +// +// maxpool1d_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/20/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +func maxPoolVerify(input: Tensor, kernelSize: [Int], stride: [Int], padMode: PaddingMode, order: TensorChannelOrder) -> Tensor { + let inputShapeArray = input.shape.shapeArray + let (channel, inHeight, inWidth) = parseImgChannelShapeInfo(order, shapeArray: inputShapeArray) + var outShapeArray = [channel, + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1])] + if order == TensorChannelOrder.Last { + outShapeArray = [ + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1]), + channel] + } + let tensor = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: outShapeArray)) + let (_, outHeight, outWidth) = parseImgChannelShapeInfo(order, shapeArray: outShapeArray) + + for c in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/op/nn/pooling/pool2d_op_test.swift b/Tests/SerranoTests/op/nn/pooling/pool2d_op_test.swift new file mode 100644 index 0000000..27a0000 --- /dev/null +++ b/Tests/SerranoTests/op/nn/pooling/pool2d_op_test.swift @@ -0,0 +1,379 @@ +// +// pool1d_op_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 7/20/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano + +public class OperatorDelegateConvPool2DOp: OperatorDelegateConv { + public var kernelSize: [Int] = [1] + public var stride: [Int] = [1] + public var padMode: PaddingMode = .Valid + public var startTime : CFAbsoluteTime = 0.0 + public var channelOrder: TensorChannelOrder = .Last + public required override init() { + super.init() + } + + override public func compare() { + fatalError() + } +} + +class Pool2DOperatorTest: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testAll() { + testInit() + testOutputShape() + testCompute() + } + + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + Target: + init.... + */ + func testInit() { + let numCase = 100 + for i in 0.. [TensorShape]? + */ + func testOutputShape() { + let numCase = 100 + for i in 0.. (check: Bool, msg: String) + */ + func testInputOutputTensorsCheck() { + let numCase = 100 + for i in 0.. Tensor { + let inputShapeArray = input.shape.shapeArray + let (channel, inHeight, inWidth) = parseImgChannelShapeInfo(order, shapeArray: inputShapeArray) + var outShapeArray = [channel, + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1])] + if order == TensorChannelOrder.Last { + outShapeArray = [ + kernelScanningOutSize(padMode, inputSize: inHeight, + kernelSize: kernelSize[0], stride: stride[0]), + kernelScanningOutSize(padMode, inputSize: inWidth, + kernelSize: kernelSize[1], stride: stride[1]), + channel] + } + let tensor = Tensor(repeatingValue: 0.0, tensorShape: TensorShape(dataType: .float, shape: outShapeArray)) + let (_, outHeight, outWidth) = parseImgChannelShapeInfo(order, shapeArray: outShapeArray) + + for c in 0..() + testCase.testAll() + } + +} diff --git a/Tests/SerranoTests/tools.swift b/Tests/SerranoTests/tools.swift new file mode 100644 index 0000000..6ecf2c8 --- /dev/null +++ b/Tests/SerranoTests/tools.swift @@ -0,0 +1,183 @@ +// +// test_utils.swift +// serrano +// +// Created by ZHONGHAO LIU on 3/18/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import Foundation +import XCTest +import Accelerate +@testable import Serrano + +public func randomInt(_ range: [Int]) -> Int { + return Int(arc4random_uniform(UInt32(range[1] - range[0]))) + range[0] +} + +public func randomFloat() -> Float { + return Float(drand48() * 2.0 - 1.0) +} + +public func randomDouble() -> Double { + return drand48() * 2.0 - 1.0 // -1.0 to 1.0 +} + +public func randomString(length: Int) -> String { + let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + let len = UInt32(letters.length) + + var randomString = "" + + for _ in 0 ..< length { + let rand = arc4random_uniform(len) + var nextChar = letters.character(at: Int(rand)) + randomString += NSString(characters: &nextChar, length: 1) as String + } + + return randomString +} + + + +/// Generate random shape object +/// +/// - Parameters: +/// - dimensions: # of dimension +/// - dimensionSizeRange: dimension size +/// - Returns: `TensorShape` +public func randomShape(dimensions: Int, dimensionSizeRange:[Int], dataType: TensorDataType) -> TensorShape { + var shape = [Int]() + for _ in 0.. ([Any], [Float]) { + var array = [Any]() + var flatArray = [Float]() + let currDimSize = shapeArray[0] + + // last dim + if shapeArray.count == 1 { + for _ in 0.. [Float] { + var flatArray = Array(repeating: Float(0.0), count: shapeArray.reduce(1, *)) + + switch dataType { + case .float: + for i in 0.. Tensor { + let shape = randomShape(dimensions: dimensions, dimensionSizeRange: dimensionSizeRange, dataType: dataType) + let tensor = SerranoResourceManager.globalManager.allocateTensors( [shape]).first! + tensor.reloadData(fromFlatArray: generateFlatArrayFromShape(shapeArray: shape.shapeArray, dataType: dataType), tensorShape: shape) + return tensor +} + +public func randomTensor(fromShape shape: TensorShape) -> Tensor { + let tensor = SerranoResourceManager.globalManager.allocateUnamangedTensor(shape) + tensor.reloadData(fromFlatArray: generateFlatArrayFromShape(shapeArray: shape.shapeArray, dataType: shape.dataType), tensorShape: shape) + return tensor +} + + +public class OperatorDelegateConv: OperatorCalculationDelegate { + public func operatorDidEndGradsComputation(_ op: ComputableOperator, grads: [String : DataSymbolSupportedDataType]) { + + } + + + public func operatorDidEndGradsComputation(_ op: ComputableOperator, outputTensors tensors: [Tensor]) { + fatalError("") + } + + public func operatorWillBeginGradsComputation(_ op: ComputableOperator) { + fatalError() + } + + + public var resultTensors: [Tensor] = [Tensor]() + public var veryfyTensors: [Tensor] = [Tensor]() + + public var dispatchGroup: DispatchGroup? = nil + + public func operatorWillBeginComputation(_ op: ComputableOperator) { + print("Operator will begin computation \(op.operatorLabel)") + } + + public func operatorDidEndComputation(_ op: ComputableOperator, outputTensors tensors: [Tensor]){ + print("Operator did end computation \(op.operatorLabel)") + self.resultTensors = tensors + + print("start compare") + self.compare() + print("Finish compare") + + self.dispatchGroup!.leave() + } + + + public func compare() { + fatalError("Need override") + } +} + + + + + + + + + + diff --git a/Tests/SerranoTests/utils/TestLibrary.swift b/Tests/SerranoTests/utils/TestLibrary.swift new file mode 100644 index 0000000..22c3de8 --- /dev/null +++ b/Tests/SerranoTests/utils/TestLibrary.swift @@ -0,0 +1,27 @@ +// +// TestLibrary.swift +// serrano +// +// Created by ZHONGHAO LIU on 11/2/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +@testable import Serrano +class TestLibrary: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testLibImport() { + libImport() + } + +} diff --git a/Tests/SerranoTests/utils/metal_hardwares_test.swift b/Tests/SerranoTests/utils/metal_hardwares_test.swift new file mode 100644 index 0000000..8170e13 --- /dev/null +++ b/Tests/SerranoTests/utils/metal_hardwares_test.swift @@ -0,0 +1,59 @@ +// +// metal_hardwares_test.swift +// serrano +// +// Created by ZHONGHAO LIU on 4/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import XCTest +import Metal +@testable import Serrano + +class metal_hardwares_test: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + +// func testExample() { +// // This is an example of a functional test case. +// // Use XCTAssert and related functions to verify your tests produce the correct results. +// } +// +// func testPerformanceExample() { +// // This is an example of a performance test case. +// self.measure { +// // Put the code you want to measure the time of here. +// } +// } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + /** + Target: + public static func tensorSizeFitCheck(tensor: Tensor) -> (result: Bool, info: String) + */ +// func testSizeFitCheck() { +// let caseNum = 5 +// +// for i in 0.. Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/Tests/TestingHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/TestingHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..118c98f --- /dev/null +++ b/Tests/TestingHostApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Tests/TestingHostApp/Base.lproj/LaunchScreen.storyboard b/Tests/TestingHostApp/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..fdf3f97 --- /dev/null +++ b/Tests/TestingHostApp/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/TestingHostApp/Base.lproj/Main.storyboard b/Tests/TestingHostApp/Base.lproj/Main.storyboard new file mode 100644 index 0000000..216cfe9 --- /dev/null +++ b/Tests/TestingHostApp/Base.lproj/Main.storyboard @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/TestingHostApp/Info.plist b/Tests/TestingHostApp/Info.plist new file mode 100644 index 0000000..38e98af --- /dev/null +++ b/Tests/TestingHostApp/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Tests/TestingHostApp/README.md b/Tests/TestingHostApp/README.md new file mode 100644 index 0000000..0155308 --- /dev/null +++ b/Tests/TestingHostApp/README.md @@ -0,0 +1 @@ +The TestingHostApp is just an empty app only being used for unit test. \ No newline at end of file diff --git a/Tests/TestingHostApp/ViewController.swift b/Tests/TestingHostApp/ViewController.swift new file mode 100644 index 0000000..1efebf0 --- /dev/null +++ b/Tests/TestingHostApp/ViewController.swift @@ -0,0 +1,25 @@ +// +// ViewController.swift +// TestingHostApp +// +// Created by ZHONGHAO LIU on 3/23/17. +// Copyright © 2017 ZHONGHAO LIU. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..83c3f25 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,21 @@ +# Live docs +A live version of latest documentation is hosted at http://serrano-lib.org/docs/ + +# Generate local docs +1. Install [mkdocs](http://www.mkdocs.org/) to generate guides and tutorials +```bash +$ pip install mkdocs +``` +2. Install [realm/jazzy](https://github.com/realm/jazzy) to generate APIs and classes reference +```bash +$ [sudo] gem install jazzy +``` +2. Clone repo and generate docs +```bash +$ git clone https://github.com/pcpLiu/serrano +$ cd serrano/ +$ chmod u+x docs/doc_generate.sh +$ ./docs/doc_generate.sh +``` + +The generated docs are located in `docs/` folder. \ No newline at end of file diff --git a/docs/doc_generate.sh b/docs/doc_generate.sh new file mode 100755 index 0000000..bc28996 --- /dev/null +++ b/docs/doc_generate.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +jazzy \ + --clean \ + --author pcpLiu \ + --author_url https://github.com/pcpLiu \ + --github_url https://github.com/pcpLiu/Serrano \ + --github-file-prefix https://github.com/pcpLiu/Serrano/tree/v0.1.0-alpha \ + --module-version v0.1.0-alpha \ + --xcodebuild-arguments -scheme,SerranoFramework \ + --module Serrano \ + --root-url http://serrano-lib.org/docs/v0.1.0-alpha/api/ \ + --output docs/v0.1.0-alpha/api/ \ + --theme fullwidth \ No newline at end of file diff --git a/docs/fullwidth/README.md b/docs/fullwidth/README.md new file mode 100644 index 0000000..f72c1bc --- /dev/null +++ b/docs/fullwidth/README.md @@ -0,0 +1,2 @@ +# Theme +Download from [jazzy](https://github.com/realm/jazzy) and modified some files for customization. \ No newline at end of file diff --git a/docs/fullwidth/assets/css/highlight.css.scss b/docs/fullwidth/assets/css/highlight.css.scss new file mode 100755 index 0000000..7bc1f29 --- /dev/null +++ b/docs/fullwidth/assets/css/highlight.css.scss @@ -0,0 +1,63 @@ +/* Credit to https://gist.github.com/wataru420/2048287 */ + +.highlight { + .c { color: #999988; font-style: italic } /* Comment */ + .err { color: #a61717; background-color: #e3d2d2 } /* Error */ + .k { color: #000000; font-weight: bold } /* Keyword */ + .o { color: #000000; font-weight: bold } /* Operator */ + .cm { color: #999988; font-style: italic } /* Comment.Multiline */ + .cp { color: #999999; font-weight: bold } /* Comment.Preproc */ + .c1 { color: #999988; font-style: italic } /* Comment.Single */ + .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */ + .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ + .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */ + .ge { color: #000000; font-style: italic } /* Generic.Emph */ + .gr { color: #aa0000 } /* Generic.Error */ + .gh { color: #999999 } /* Generic.Heading */ + .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ + .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */ + .go { color: #888888 } /* Generic.Output */ + .gp { color: #555555 } /* Generic.Prompt */ + .gs { font-weight: bold } /* Generic.Strong */ + .gu { color: #aaaaaa } /* Generic.Subheading */ + .gt { color: #aa0000 } /* Generic.Traceback */ + .kc { color: #000000; font-weight: bold } /* Keyword.Constant */ + .kd { color: #000000; font-weight: bold } /* Keyword.Declaration */ + .kp { color: #000000; font-weight: bold } /* Keyword.Pseudo */ + .kr { color: #000000; font-weight: bold } /* Keyword.Reserved */ + .kt { color: #445588; } /* Keyword.Type */ + .m { color: #009999 } /* Literal.Number */ + .s { color: #d14 } /* Literal.String */ + .na { color: #008080 } /* Name.Attribute */ + .nb { color: #0086B3 } /* Name.Builtin */ + .nc { color: #445588; font-weight: bold } /* Name.Class */ + .no { color: #008080 } /* Name.Constant */ + .ni { color: #800080 } /* Name.Entity */ + .ne { color: #990000; font-weight: bold } /* Name.Exception */ + .nf { color: #990000; } /* Name.Function */ + .nn { color: #555555 } /* Name.Namespace */ + .nt { color: #000080 } /* Name.Tag */ + .nv { color: #008080 } /* Name.Variable */ + .ow { color: #000000; font-weight: bold } /* Operator.Word */ + .w { color: #bbbbbb } /* Text.Whitespace */ + .mf { color: #009999 } /* Literal.Number.Float */ + .mh { color: #009999 } /* Literal.Number.Hex */ + .mi { color: #009999 } /* Literal.Number.Integer */ + .mo { color: #009999 } /* Literal.Number.Oct */ + .sb { color: #d14 } /* Literal.String.Backtick */ + .sc { color: #d14 } /* Literal.String.Char */ + .sd { color: #d14 } /* Literal.String.Doc */ + .s2 { color: #d14 } /* Literal.String.Double */ + .se { color: #d14 } /* Literal.String.Escape */ + .sh { color: #d14 } /* Literal.String.Heredoc */ + .si { color: #d14 } /* Literal.String.Interpol */ + .sx { color: #d14 } /* Literal.String.Other */ + .sr { color: #009926 } /* Literal.String.Regex */ + .s1 { color: #d14 } /* Literal.String.Single */ + .ss { color: #990073 } /* Literal.String.Symbol */ + .bp { color: #999999 } /* Name.Builtin.Pseudo */ + .vc { color: #008080 } /* Name.Variable.Class */ + .vg { color: #008080 } /* Name.Variable.Global */ + .vi { color: #008080 } /* Name.Variable.Instance */ + .il { color: #009999 } /* Literal.Number.Integer.Long */ +} diff --git a/docs/fullwidth/assets/css/jazzy.css.scss b/docs/fullwidth/assets/css/jazzy.css.scss new file mode 100755 index 0000000..fdba118 --- /dev/null +++ b/docs/fullwidth/assets/css/jazzy.css.scss @@ -0,0 +1,615 @@ +// =========================================================================== +// +// Variables +// +// =========================================================================== + +$body_background: #fff; +$body_font: 16px/1.7 'Helvetica Neue', Helvetica, Arial, sans-serif; +$text_color: #333; +$gray_border: 1px solid #ddd; + +$heading_weight: 700; +$light_heading_color: #777; + +$quote_color: #858585; +$quote_border: 4px solid #e5e5e5; + +$link_color: #4183c4; + +$table_alt_row_color: #fbfbfb; +$table_border_color: #ddd; + +$code_bg_color: #f7f7f7; +$code_font: Consolas, "Liberation Mono", Menlo, Courier, monospace; + + +// ----- Layout + +$gutter: 16px; +$navigation_max_width: 300px; + + +// ----- Header + +$header_bg_color: #444; +$header_link_color: #fff; +$doc_coverage_color: #999; + + +// ----- Breadcrumbs + +$breadcrumbs_bg_color: #fbfbfb; +$breadcrumbs_border_color: #ddd; + + +// ----- Navigation + +$navigation_max_width: 300px; +$navigation_bg_color: #fbfbfb; +$navigation_border_color: #ddd; +$navigation_title_color: #333; +$navigation_task_color: #808080; + +// ----- Content + +$declaration_title_language_color: #4183c4; +$declaration_language_border: 5px solid #cde9f4; +$declaration_bg_color: #fff; +$declaration_border_color: #ddd; + +$aside_color: #aaa; +$aside_border: 5px solid lighten($aside_color, 20%); +$aside_warning_color: #ff0000; +$aside_warning_border: 5px solid lighten($aside_warning_color, 20%); + +// ----- Footer + +$footer_bg_color: #444; +$footer_text_color: #ddd; +$footer_link_color: #fff; + + +// =========================================================================== +// +// Base +// +// =========================================================================== + +*, *:before, *:after { + box-sizing: inherit; +} + +body { + margin: 0; + background: $body_background; + color: $text_color; + font: $body_font; + letter-spacing: .2px; + -webkit-font-smoothing: antialiased; + box-sizing: border-box; +} + +// ----- Block elements + +@mixin heading($font-size: 1rem, $margin: 1.275em 0 0.85em) { + font-size: $font-size; + font-weight: $heading_weight; + margin: $margin; +} + +h1 { + @include heading(2rem, 1.275em 0 0.6em); +} + +h2 { + @include heading(1.75rem, 1.275em 0 0.3em); +} + +h3 { + @include heading(1.5rem, 1em 0 0.3em); +} + +h4 { + @include heading(1.25rem); +} + +h5 { + @include heading; +} + +h6 { + @include heading; + color: $light_heading_color; +} + +p { + margin: 0 0 1em; +} + +ul, ol { + padding: 0 0 0 2em; + margin: 0 0 0.85em; +} + +blockquote { + margin: 0 0 0.85em; + padding: 0 15px; + color: $quote_color; + border-left: $quote_border; +} + + +// ----- Inline elements + +img { + max-width: 100%; +} + +a { + color: $link_color; + text-decoration: none; + + &:hover, &:focus { + outline: 0; + text-decoration: underline; + } +} + + +// ----- Tables + +table { + background: $body_background; + width: 100%; + border-collapse: collapse; + border-spacing: 0; + overflow: auto; + margin: 0 0 0.85em; +} + +tr { + &:nth-child(2n) { + background-color: $table_alt_row_color; + } +} + +th, td { + padding: 6px 13px; + border: 1px solid $table_border_color; +} + + +// ----- Code + +pre { + margin: 0 0 1.275em; + padding: .85em 1em; + overflow: auto; + background: $code_bg_color; + font-size: .85em; + font-family: $code_font; +} + +code { + font-family: $code_font; +} + +p, li { + > code { + background: $code_bg_color; + padding: .2em; + &:before, &:after { + letter-spacing: -.2em; + content: "\00a0"; + } + } +} + +pre code { + padding: 0; + white-space: pre; +} + + +// =========================================================================== +// +// Layout +// +// =========================================================================== + +.content-wrapper { + display: flex; + flex-direction: column; + @media (min-width: 768px) { + flex-direction: row; + } +} + + +// =========================================================================== +// +// Header +// +// =========================================================================== + +.header { + display: flex; + padding: $gutter/2; + font-size: 0.875em; + background: $header_bg_color; + color: $doc_coverage_color; +} + +.header-col { + margin: 0; + padding: 0 $gutter/2 +} + +.header-col--primary { + flex: 1; +} + +.header-link { + color: $header_link_color; +} + +.header-icon { + padding-right: 6px; + vertical-align: -4px; + height: 16px; +} + + + +// =========================================================================== +// +// Breadcrumbs +// +// =========================================================================== + +.breadcrumbs { + font-size: 0.875em; + padding: $gutter / 2 $gutter; + margin: 0; + background: $breadcrumbs_bg_color; + border-bottom: 1px solid $breadcrumbs_border_color; +} + +.carat { + height: 10px; + margin: 0 5px; +} + + +// =========================================================================== +// +// Navigation +// +// =========================================================================== + +.navigation { + order: 2; + + @media (min-width: 768px) { + order: 1; + width: 25%; + max-width: $navigation_max_width; + padding-bottom: $gutter*4; + overflow: hidden; + word-wrap: normal; + background: $navigation_bg_color; + border-right: 1px solid $navigation_border_color; + } +} + +.nav-groups { + list-style-type: none; + padding-left: 0; +} + +.nav-group-name { + border-bottom: 1px solid $navigation_border_color; + padding: $gutter/2 0 $gutter/2 $gutter; +} + +.nav-group-name-link { + color: $navigation_title_color; +} + +.nav-group-tasks { + margin: $gutter/2 0; + padding: 0 0 0 $gutter/2; +} + +.nav-group-task { + font-size: 1em; + list-style-type: none; + white-space: nowrap; +} + +.nav-group-task-link { + color: $navigation_task_color; +} + +// =========================================================================== +// +// Content +// +// =========================================================================== + +.main-content { + order: 1; + @media (min-width: 768px) { + order: 2; + flex: 1; + padding-bottom: 60px; + } +} + +.section { + padding: 0 $gutter * 2; + border-bottom: 1px solid $navigation_border_color; +} + +.section-content { + max-width: 834px; + margin: 0 auto; + padding: $gutter 0; +} + +.section-name { + color: #666; + display: block; +} + +.declaration .highlight { + overflow-x: initial; // This allows the scrollbar to show up inside declarations + padding: $gutter/2 0; + margin: 0; + background-color: transparent; + border: none; +} + +.task-group-section { + border-top: $gray_border; +} + +.task-group { + padding-top: 0px; +} + +.task-name-container { + a[name] { + &:before { + content: ""; + display: block; + } + } +} + +.item-container { + padding: 0; +} + +.item { + padding-top: 8px; + width: 100%; + list-style-type: none; + + a[name] { + &:before { + content: ""; + display: block; + } + } + + .token { + padding-left: 3px; + margin-left: 0px; + font-size: 1rem; + } + + .declaration-note { + font-size: .85em; + color: #808080; + font-style: italic; + } +} + +.pointer-container { + border-bottom: $gray_border; + left: -23px; + padding-bottom: 13px; + position: relative; + width: 110%; +} + +.pointer { + left: 21px; + top: 7px; + display: block; + position: absolute; + width: 12px; + height: 12px; + border-left: 1px solid $declaration_border_color; + border-top: 1px solid $declaration_border_color; + background: $declaration_bg_color; + transform: rotate(45deg); +} + +.height-container { + display: none; + position: relative; + width: 100%; + overflow: hidden; + .section { + background: $declaration_bg_color; + border: $gray_border; + border-top-width: 0; + padding-top: 10px; + padding-bottom: 5px; + padding: $gutter / 2 $gutter; + } +} + +.aside, .language { + padding: 6px 12px; + margin: 12px 0; + border-left: $aside_border; + overflow-y: hidden; + .aside-title { + font-size: 9px; + letter-spacing: 2px; + text-transform: uppercase; + padding-bottom: 0; + margin: 0; + color: $aside_color; + -webkit-user-select: none; + } + p:last-child { + margin-bottom: 0; + } +} + +.language { + border-left: $declaration_language_border; + .aside-title { + color: $declaration_title_language_color; + } +} + +.aside-warning { + border-left: $aside_warning_border; + .aside-title { + color: $aside_warning_color; + } +} + +.graybox { + border-collapse: collapse; + width: 100%; + p { + margin: 0; + word-break: break-word; + min-width: 50px; + } + td { + border: $gray_border; + padding: 5px 25px 5px 10px; + vertical-align: middle; + } + tr td:first-of-type { + text-align: right; + padding: 7px; + vertical-align: top; + word-break: normal; + width: 40px; + } +} + +.slightly-smaller { + font-size: 0.9em; +} + + +// =========================================================================== +// +// Footer +// +// =========================================================================== + +.footer { + padding: $gutter/2 $gutter; + background: $footer_bg_color; + color: $footer_text_color; + font-size: 0.8em; + + p { + margin: $gutter/2 0; + } + + a { + color: $footer_link_color; + } +} + + +// =========================================================================== +// +// Dash +// +// =========================================================================== + +html.dash { + + .header, .breadcrumbs, .navigation { + display: none; + } + + .height-container { + display: block; + } +} + +// =========================================================================== +// +// Search +// +// =========================================================================== +form[role=search] { + input { + font: $body_font; + font-size: 14px; + line-height: 24px; + padding: 0 10px; + margin: 0; + border: none; + border-radius: 1em; + .loading & { + background: white url(../img/spinner.gif) center right 4px no-repeat; + } + } + + // Typeahead elements + + .tt-menu { + margin: 0; + min-width: 300px; + background: $navigation_bg_color; + color: $text_color; + border: 1px solid $navigation_border_color; + } + + .tt-highlight { + font-weight: bold; + } + + .tt-suggestion { + font: $body_font; + padding: 0 $gutter/2; + span { + display: table-cell; + white-space: nowrap; + } + .doc-parent-name { + width: 100%; + text-align: right; + font-weight: normal; + font-size: 0.9em; + padding-left: $gutter; + } + } + + .tt-suggestion:hover, + .tt-suggestion.tt-cursor { + cursor: pointer; + background-color: $link_color; + color: #fff; + } + + .tt-suggestion:hover .doc-parent-name, + .tt-suggestion.tt-cursor .doc-parent-name { + color: #fff; + } +} \ No newline at end of file diff --git a/docs/fullwidth/assets/img/carat.png b/docs/fullwidth/assets/img/carat.png new file mode 100755 index 0000000..29d2f7f Binary files /dev/null and b/docs/fullwidth/assets/img/carat.png differ diff --git a/docs/fullwidth/assets/img/dash.png b/docs/fullwidth/assets/img/dash.png new file mode 100755 index 0000000..6f694c7 Binary files /dev/null and b/docs/fullwidth/assets/img/dash.png differ diff --git a/docs/fullwidth/assets/img/gh.png b/docs/fullwidth/assets/img/gh.png new file mode 100755 index 0000000..628da97 Binary files /dev/null and b/docs/fullwidth/assets/img/gh.png differ diff --git a/docs/fullwidth/assets/img/spinner.gif b/docs/fullwidth/assets/img/spinner.gif new file mode 100755 index 0000000..e3038d0 Binary files /dev/null and b/docs/fullwidth/assets/img/spinner.gif differ diff --git a/docs/fullwidth/assets/js/jazzy.js b/docs/fullwidth/assets/js/jazzy.js new file mode 100755 index 0000000..009c80d --- /dev/null +++ b/docs/fullwidth/assets/js/jazzy.js @@ -0,0 +1,43 @@ +window.jazzy = {'docset': false} +if (typeof window.dash != 'undefined') { + document.documentElement.className += ' dash' + window.jazzy.docset = true +} +if (navigator.userAgent.match(/xcode/i)) { + document.documentElement.className += ' xcode' + window.jazzy.docset = true +} + +// On doc load, toggle the URL hash discussion if present +$(document).ready(function() { + if (!window.jazzy.docset) { + var linkToHash = $('a[href="' + window.location.hash +'"]'); + linkToHash.trigger("click"); + } +}); + +// On token click, toggle its discussion and animate token.marginLeft +$(".token").click(function(event) { + if (window.jazzy.docset) { + return; + } + var link = $(this); + var animationDuration = 300; + $content = link.parent().parent().next(); + $content.slideToggle(animationDuration); + + // Keeps the document from jumping to the hash. + var href = $(this).attr('href'); + if (history.pushState) { + history.pushState({}, '', href); + } else { + location.hash = href; + } + event.preventDefault(); +}); + +// Dumb down quotes within code blocks that delimit strings instead of quotations +// https://github.com/realm/jazzy/issues/714 +$("code q").replaceWith(function () { + return ["\"", $(this).contents(), "\""]; +}); diff --git a/docs/fullwidth/assets/js/jazzy.search.js b/docs/fullwidth/assets/js/jazzy.search.js new file mode 100755 index 0000000..54be83c --- /dev/null +++ b/docs/fullwidth/assets/js/jazzy.search.js @@ -0,0 +1,62 @@ +$(function(){ + var searchIndex = lunr(function() { + this.ref('url'); + this.field('name'); + }); + + var $typeahead = $('[data-typeahead]'); + var $form = $typeahead.parents('form'); + var searchURL = $form.attr('action'); + + function displayTemplate(result) { + return result.name; + } + + function suggestionTemplate(result) { + var t = '
'; + t += '' + result.name + ''; + if (result.parent_name) { + t += '' + result.parent_name + ''; + } + t += '
'; + return t; + } + + $typeahead.one('focus', function() { + $form.addClass('loading'); + + $.getJSON(searchURL).then(function(searchData) { + $.each(searchData, function (url, doc) { + searchIndex.add({url: url, name: doc.name}); + }); + + $typeahead.typeahead( + { + highlight: true, + minLength: 3 + }, + { + limit: 10, + display: displayTemplate, + templates: { suggestion: suggestionTemplate }, + source: function(query, sync) { + var results = searchIndex.search(query).map(function(result) { + var doc = searchData[result.ref]; + doc.url = result.ref; + return doc; + }); + sync(results); + } + } + ); + $form.removeClass('loading'); + $typeahead.trigger('focus'); + }); + }); + + var baseURL = searchURL.slice(0, -"search.json".length); + + $typeahead.on('typeahead:select', function(e, result) { + window.location = baseURL + result.url; + }); +}); diff --git a/docs/fullwidth/assets/js/jquery.min.js b/docs/fullwidth/assets/js/jquery.min.js new file mode 100755 index 0000000..ab28a24 --- /dev/null +++ b/docs/fullwidth/assets/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; +if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("