Skip to content

Commit

Permalink
QOL stuff, and...
Browse files Browse the repository at this point in the history
- added some documentation
- added `trailingNonzeroByteCount` and `trailingZeroByteCount` computed properties to `SIMD`
- now working on using SIMD to parse http requests
  • Loading branch information
RandomHashTags committed Nov 8, 2024
1 parent 0db4a6d commit 38d4252
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 64 deletions.
3 changes: 2 additions & 1 deletion Sources/Destiny/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ enum ClientProcessing {
shutdown(client, 2) // shutdown read and write (https://www.gnu.org/software/libc/manual/html_node/Closing-a-Socket.html)
close(client)
}
//let request:Request = try client_socket.loadRequest()
let token:DestinyRoutePathType = try client_socket.readLineSIMD()
if let responder:StaticRouteResponseProtocol = static_responses[token] {
if responder.isAsync {
Expand All @@ -156,7 +157,7 @@ enum ClientProcessing {
}
} else if let (start_line, route):(HTTPStartLine, DynamicRouteResponseProtocol) = dynamic_responses[token] {
let headers:[String:String] = try client_socket.readHeaders()
let request:Request = Request(method: start_line.method, path: start_line.path, version: start_line.version, headers: headers, body: "")
let request:Request = Request(token: token, method: start_line.method, path: start_line.path, version: start_line.version, headers: headers, body: "")

var response:DynamicResponseProtocol = route.defaultResponse
for index in route.parameterPathIndexes {
Expand Down
74 changes: 66 additions & 8 deletions Sources/Destiny/Socket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,45 @@ public extension Socket {
return line
}

/// Reads `scalarCount` characters and loads them into the target SIMD.
@inlinable
func readLineSIMD<T: SIMD>() throws -> T where T.Scalar: BinaryInteger { // read just the method, path & http version
func readLineSIMD2<T : SIMD>() throws -> (T, Int) where T.Scalar == UInt8 { // read just the method, path & http version
var string:T = T()
var i:Int = 0, char:UInt8 = 0
let read:Int = try withUnsafeMutableBytes(of: &string) { p in
return try readSIMDBuffer(into: p.baseAddress!, length: T.scalarCount)
}
return (string, read)
}

func loadRequest() throws -> Request {
var test:[SIMD64<UInt8>] = []
test.reserveCapacity(10) // maximum of 640 bytes; decent starting point
var head_count:Int = 0
var token:DestinyRoutePathType = .init()
while true {
char = try readByte()
if char == 10 || i == T.scalarCount {
let (line, read):(SIMD64<UInt8>, Int) = try readLineSIMD2()
if head_count == 0 {
token = line.lowHalf
}
if read == 0 {
break
} else if char == 13 {
continue
}
string[i] = T.Scalar(char)
i += 1
test.append(line)
head_count += read
if read < 64 {
break
}
}
print("test strings=\(test.map({ $0.string() }))")
return Request(token: token, method: .get, path: ["dynamic", "rekt"], version: "HTTP/1.1", headers: [:], body: "")
}

/// Reads `scalarCount` characters and loads them into the target SIMD.
@inlinable
func readLineSIMD<T : SIMD>() throws -> T where T.Scalar == UInt8 { // read just the method, path & http version
var string:T = T()
let _:Int = try withUnsafeMutableBytes(of: &string) { p in
return try readBuffer(into: p.baseAddress!, length: T.scalarCount)
}
return string
}
Expand All @@ -95,6 +121,7 @@ public extension Socket {
guard let baseAddress:UnsafeMutablePointer<UInt8> = buffer.baseAddress else { return 0 }
return try readBuffer(into: baseAddress, length: length)
}

/// Reads multiple bytes and writes them into a buffer
@inlinable
func readBuffer(into baseAddress: UnsafeMutablePointer<UInt8>, length: Int) throws -> Int {
Expand All @@ -112,6 +139,37 @@ public extension Socket {
}
return bytes_read
}
/// Reads multiple bytes and writes them into a buffer
@inlinable
func readBuffer(into baseAddress: UnsafeMutableRawPointer, length: Int) throws -> Int {
var bytes_read:Int = 0
while bytes_read < length {
if Task.isCancelled { return 0 }
let to_read:Int = min(Self.bufferLength, length - bytes_read)
let read_bytes:Int = recv(fileDescriptor, baseAddress + bytes_read, to_read, 0)
if read_bytes < 0 { // error
throw SocketError.readBufferFailed()
} else if read_bytes == 0 { // end of file
break
}
bytes_read += read_bytes
}
return bytes_read
}

/// Reads multiple bytes and writes them into a buffer
@inlinable
func readSIMDBuffer(into baseAddress: UnsafeMutableRawPointer, length: Int) throws -> Int {
var bytes_read:Int = 0
if Task.isCancelled { return 0 }
let to_read:Int = min(Self.bufferLength, length - bytes_read)
let read_bytes:Int = recv(fileDescriptor, baseAddress + bytes_read, to_read, 0)
if read_bytes < 0 { // error
throw SocketError.readBufferFailed()
}
bytes_read += read_bytes
return bytes_read
}
}

// MARK: Socket writing
Expand Down
5 changes: 3 additions & 2 deletions Sources/DestinyUtilities/DynamicResponses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ public struct DynamicResponses : Sendable {
return nil
}
let values:[String] = spaced[1].splitSIMD(separator: 47).map({ $0.string() }) // 1 = the target route path; 47 = slash
let start_line:HTTPStartLine = HTTPStartLine(method: method, path: values, version: version)
if let responder:DynamicRouteResponseProtocol = parameterless[token] {
return (HTTPStartLine(method: method, path: values, version: version), responder)
return (start_line, responder)
}
guard let routes:[DynamicRouteResponseProtocol] = parameterized.get(values.count) else { return nil }
for route in routes {
Expand All @@ -54,7 +55,7 @@ public struct DynamicResponses : Sendable {
}
}
if found {
return (HTTPStartLine(method: method, path: values, version: version), route)
return (start_line, route)
}
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/DestinyUtilities/DynamicRoute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public extension DynamicRoute {
method_string = argument.expression.memberAccess!.declName.baseName.text.uppercased()
break
case "path":
path = argument.expression.array!.elements.map({ PathComponent.parse($0.expression) })
path = argument.expression.array!.elements.map({ PathComponent(expression: $0.expression) })
for component in path.filter({ $0.isParameter }) {
parameters[component.value] = ""
}
Expand Down
30 changes: 9 additions & 21 deletions Sources/DestinyUtilities/PathComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import SwiftSyntax

/// Represents an individual path value for a route. Used to determine how to handle a route responder for dynamic routes with parameters at compile time.
public enum PathComponent : Sendable, CustomStringConvertible, ExpressibleByStringLiteral {
public typealias StringLiteralType = String
public typealias ExtendedGraphemeClusterLiteralType = String
Expand All @@ -15,53 +16,40 @@ public enum PathComponent : Sendable, CustomStringConvertible, ExpressibleByStri
case literal(String)
case parameter(String)

public init(expression: ExprSyntax) {
self = .init(stringLiteral: expression.stringLiteral?.string ?? expression.functionCall!.calledExpression.memberAccess!.declName.baseName.text)
}
public init(stringLiteral value: String) {
if value.first == ":" {
self = .parameter(String(value[value.index(after: value.startIndex)...]))
self = .parameter(value[value.index(after: value.startIndex)...].replacingOccurrences(of: ":", with: ""))
} else {
self = .literal(value)
self = .literal(value.replacingOccurrences(of: ":", with: ""))
}
}

public var description : String { "\"" + slug + "\"" }

/// Whether or not this component is a parameter.
public var isParameter : Bool {
switch self {
case .literal(_): return false
case .parameter(_): return true
}
}

/// String representation of this component including the delimiter, if it is a parameter. Used to determine where parameters are located in a route's path at compile time.
public var slug : String {
switch self {
case .literal(let value): return value
case .parameter(let value): return ":" + value
}
}

/// String representation of this component where the delimiter is omitted (only the name of the path is present).
public var value : String {
switch self {
case .literal(let value): return value
case .parameter(let value): return value
}
}
}

public extension PathComponent {
static func parse(_ expression: ExprSyntax) -> Self {
if var string:String = expression.stringLiteral?.string {
let is_parameter:Bool = string[string.startIndex] == ":"
string.replace(":", with: "")
return is_parameter ? .parameter(string) : .literal(string)
} else {
let function:FunctionCallExprSyntax = expression.functionCall!
let target:String = function.calledExpression.memberAccess!.declName.baseName.text
let value:String = function.arguments.first!.expression.stringLiteral!.string.replacing(":", with: "")
switch target {
case "literal": return .literal(value)
case "parameter": return .parameter(value)
default: return .literal(value)
}
}
}
}
4 changes: 3 additions & 1 deletion Sources/DestinyUtilities/SocketProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ public protocol SocketProtocol : ~Copyable {

init(fileDescriptor: Int32)

@inlinable func readLineSIMD<T: SIMD>() throws -> T where T.Scalar: BinaryInteger
@inlinable func loadRequest() throws -> Request

@inlinable func readLineSIMD<T: SIMD>() throws -> T where T.Scalar == UInt8
@inlinable func readHeaders() throws -> [String:String] // TODO: make faster (replace with a SIMD/StackString equivalent)

@inlinable func readBuffer(into baseAddress: UnsafeMutablePointer<UInt8>, length: Int) throws -> Int
Expand Down
124 changes: 124 additions & 0 deletions Sources/DestinyUtilities/StackStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,130 @@ public extension SIMD64 where Scalar : BinaryInteger {
}
}

// MARK: trailingNonzeroByteCount O(1)
// implementation should never change
public extension SIMD2 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
if y == 0 { return 0 }
if x == 0 { return 1 }
return scalarCount
}
}
public extension SIMD4 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
let all_nonzero:SIMDMask<SIMD2<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount }
if (lowHalf .!= .zero) != all_nonzero { return 2 + lowHalf.trailingNonzeroByteCount }
return scalarCount
}
}
public extension SIMD8 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
let all_nonzero:SIMDMask<SIMD4<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount }
if (lowHalf .!= .zero) != all_nonzero { return 4 + lowHalf.trailingNonzeroByteCount }
return scalarCount
}
}
public extension SIMD16 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
let all_nonzero:SIMDMask<SIMD8<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount }
if (lowHalf .!= .zero) != all_nonzero { return 8 + lowHalf.trailingNonzeroByteCount }
return scalarCount
}
}
public extension SIMD32 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
let all_nonzero:SIMDMask<SIMD16<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount }
if (lowHalf .!= .zero) != all_nonzero { return 16 + lowHalf.trailingNonzeroByteCount }
return scalarCount
}
}
public extension SIMD64 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingNonzeroByteCount : Int {
let all_nonzero:SIMDMask<SIMD32<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .!= .zero) != all_nonzero { return highHalf.trailingNonzeroByteCount }
if (lowHalf .!= .zero) != all_nonzero { return 32 + lowHalf.trailingNonzeroByteCount }
return scalarCount
}
}

// MARK: trailingZeroByteCount O(1)
// implementation should never change
public extension SIMD2 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
if y != 0 { return 0 }
if x != 0 { return 1 }
return scalarCount
}
}
public extension SIMD4 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
let all_zero:SIMDMask<SIMD2<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount }
if (lowHalf .== .zero) != all_zero { return 2 + lowHalf.trailingZeroByteCount }
return scalarCount
}
}
public extension SIMD8 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
let all_zero:SIMDMask<SIMD4<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount }
if (lowHalf .== .zero) != all_zero { return 4 + lowHalf.trailingZeroByteCount }
return scalarCount
}
}
public extension SIMD16 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
let all_zero:SIMDMask<SIMD8<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount }
if (lowHalf .== .zero) != all_zero { return 8 + lowHalf.trailingZeroByteCount }
return scalarCount
}
}
public extension SIMD32 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
let all_zero:SIMDMask<SIMD16<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount }
if (lowHalf .== .zero) != all_zero { return 16 + lowHalf.trailingZeroByteCount }
return scalarCount
}
}
public extension SIMD64 where Scalar : BinaryInteger {
/// - Complexity: O(1)
@inlinable
var trailingZeroByteCount : Int {
let all_zero:SIMDMask<SIMD32<Scalar.SIMDMaskScalar>> = .init(repeating: true)
if (highHalf .== .zero) != all_zero { return highHalf.trailingZeroByteCount }
if (lowHalf .== .zero) != all_zero { return 32 + lowHalf.trailingZeroByteCount }
return scalarCount
}
}

// MARK: hasPrefix O(1)
// implementation should never change
public extension SIMD4 where Scalar : BinaryInteger {
Expand Down
3 changes: 3 additions & 0 deletions Sources/DestinyUtilities/Utilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,22 @@ public typealias DestinyRoutePathType = StackString32

// MARK: Request
public struct Request : ~Copyable {
public let token:DestinyRoutePathType
public let method:HTTPRequest.Method
public let path:[String]
public let version:String
public let headers:[String:String]
public let body:String

public init(
token: DestinyRoutePathType,
method: HTTPRequest.Method,
path: [String],
version: String,
headers: [String:String],
body: String
) {
self.token = token
self.method = method
self.path = path
self.version = version
Expand Down
Loading

0 comments on commit 38d4252

Please sign in to comment.