feat: add Swift implementation of isspam (sisspam) with CI build and benchmark integration
- Add swift_isspam package with optimized main.swift using unsafe memory flags - Extend CI workflow to install Swift 5.9.2 and include sisspam in build_all and benchmark - Update Makefile with build_swift target and publish step for sisspam binary - Add sisspam to .gitignore and README with author credit - Enable Swift timing in bench.py alongside existing language implementations
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
// retoor <retoor@molodetz.nl>
|
||||
|
||||
import Foundation
|
||||
import Dispatch
|
||||
|
||||
struct Stats {
|
||||
var words: UInt64 = 0
|
||||
var capitalized: UInt64 = 0
|
||||
var sentences: UInt64 = 0
|
||||
var numbers: UInt64 = 0
|
||||
var forbidden: UInt64 = 0
|
||||
}
|
||||
|
||||
let charFlags: UnsafeMutablePointer<UInt8> = {
|
||||
let t = UnsafeMutablePointer<UInt8>.allocate(capacity: 256)
|
||||
t.initialize(repeating: 0, count: 256)
|
||||
t[0x09] = 1; t[0x0A] = 1; t[0x0B] = 1; t[0x0C] = 1; t[0x0D] = 1; t[0x20] = 1
|
||||
for i in 0x30...0x39 { t[i] |= 2 }
|
||||
for i in 0x41...0x5A { t[i] |= 4 }
|
||||
t[0x2E] = 8
|
||||
return t
|
||||
}()
|
||||
|
||||
let forbiddenWords: [String] = [
|
||||
"recovery", "techie", "http", "https", "digital", "hack", "::", "//", "com",
|
||||
"@", "crypto", "bitcoin", "wallet", "hacker", "welcome", "whatsapp", "email", "cryptocurrency",
|
||||
"stolen", "freeze", "quick", "crucial", "tracing", "scammers", "expers", "hire", "century",
|
||||
"transaction", "essential", "managing", "contact", "contacting", "understanding", "assets", "funds"
|
||||
]
|
||||
|
||||
struct ForbiddenLookup {
|
||||
let lengthBits: UnsafeMutablePointer<UInt16>
|
||||
let wordData: UnsafeMutablePointer<UInt64>
|
||||
let wordCount: UnsafeMutablePointer<UInt8>
|
||||
|
||||
init() {
|
||||
lengthBits = UnsafeMutablePointer<UInt16>.allocate(capacity: 256)
|
||||
wordData = UnsafeMutablePointer<UInt64>.allocate(capacity: 256 * 16)
|
||||
wordCount = UnsafeMutablePointer<UInt8>.allocate(capacity: 256)
|
||||
|
||||
lengthBits.initialize(repeating: 0, count: 256)
|
||||
wordData.initialize(repeating: 0, count: 256 * 16)
|
||||
wordCount.initialize(repeating: 0, count: 256)
|
||||
|
||||
for word in forbiddenWords {
|
||||
let bytes = Array(word.utf8)
|
||||
guard !bytes.isEmpty && bytes.count <= 14 else { continue }
|
||||
|
||||
let firstChar = Int(bytes[0])
|
||||
let len = bytes.count
|
||||
|
||||
lengthBits[firstChar] |= UInt16(1 << len)
|
||||
|
||||
var lo: UInt64 = 0
|
||||
var hi: UInt64 = 0
|
||||
for (i, b) in bytes.enumerated() {
|
||||
if i < 8 {
|
||||
lo |= UInt64(b) << (i * 8)
|
||||
} else {
|
||||
hi |= UInt64(b) << ((i - 8) * 8)
|
||||
}
|
||||
}
|
||||
|
||||
let idx = Int(wordCount[firstChar])
|
||||
if idx < 8 {
|
||||
wordData[firstChar * 16 + idx * 2] = lo
|
||||
wordData[firstChar * 16 + idx * 2 + 1] = hi
|
||||
wordCount[firstChar] = UInt8(idx + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@inline(__always)
|
||||
func check(_ ptr: UnsafePointer<UInt8>, _ len: Int) -> UInt64 {
|
||||
let firstChar = Int(ptr[0])
|
||||
if (lengthBits[firstChar] & UInt16(1 << len)) == 0 { return 0 }
|
||||
|
||||
let raw = UnsafeRawPointer(ptr)
|
||||
var lo: UInt64
|
||||
var hi: UInt64 = 0
|
||||
|
||||
if len <= 8 {
|
||||
switch len {
|
||||
case 1: lo = UInt64(ptr[0])
|
||||
case 2: lo = UInt64(raw.loadUnaligned(as: UInt16.self))
|
||||
case 3: lo = UInt64(raw.loadUnaligned(as: UInt16.self)) | (UInt64(ptr[2]) << 16)
|
||||
case 4: lo = UInt64(raw.loadUnaligned(as: UInt32.self))
|
||||
case 5: lo = UInt64(raw.loadUnaligned(as: UInt32.self)) | (UInt64(ptr[4]) << 32)
|
||||
case 6: lo = UInt64(raw.loadUnaligned(as: UInt32.self)) | (UInt64(raw.advanced(by: 4).loadUnaligned(as: UInt16.self)) << 32)
|
||||
case 7: lo = UInt64(raw.loadUnaligned(as: UInt32.self)) | (UInt64(raw.advanced(by: 4).loadUnaligned(as: UInt16.self)) << 32) | (UInt64(ptr[6]) << 48)
|
||||
default: lo = raw.loadUnaligned(as: UInt64.self)
|
||||
}
|
||||
} else {
|
||||
lo = raw.loadUnaligned(as: UInt64.self)
|
||||
switch len {
|
||||
case 9: hi = UInt64(ptr[8])
|
||||
case 10: hi = UInt64(raw.advanced(by: 8).loadUnaligned(as: UInt16.self))
|
||||
case 11: hi = UInt64(raw.advanced(by: 8).loadUnaligned(as: UInt16.self)) | (UInt64(ptr[10]) << 16)
|
||||
case 12: hi = UInt64(raw.advanced(by: 8).loadUnaligned(as: UInt32.self))
|
||||
case 13: hi = UInt64(raw.advanced(by: 8).loadUnaligned(as: UInt32.self)) | (UInt64(ptr[12]) << 32)
|
||||
default: hi = UInt64(raw.advanced(by: 8).loadUnaligned(as: UInt32.self)) | (UInt64(raw.advanced(by: 12).loadUnaligned(as: UInt16.self)) << 32)
|
||||
}
|
||||
}
|
||||
|
||||
let base = firstChar * 16
|
||||
let count = Int(wordCount[firstChar])
|
||||
for j in 0..<count {
|
||||
if wordData[base + j * 2] == lo && wordData[base + j * 2 + 1] == hi {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
let lookup = ForbiddenLookup()
|
||||
|
||||
@inline(__always)
|
||||
func analyzeBuffer(_ ptr: UnsafePointer<UInt8>, _ length: Int) -> Stats {
|
||||
var words: UInt64 = 0
|
||||
var capitalized: UInt64 = 0
|
||||
var sentences: UInt64 = 0
|
||||
var numbers: UInt64 = 0
|
||||
var forbidden: UInt64 = 0
|
||||
|
||||
var i = 0
|
||||
let end = length
|
||||
|
||||
while i < end {
|
||||
let f0 = charFlags[Int(ptr[i])]
|
||||
|
||||
sentences &+= UInt64((f0 >> 3) & 1)
|
||||
|
||||
if (f0 & 1) != 0 {
|
||||
i += 1
|
||||
continue
|
||||
}
|
||||
|
||||
let wordStart = i
|
||||
capitalized &+= UInt64((f0 >> 2) & 1)
|
||||
var hasDigit = UInt64((f0 >> 1) & 1)
|
||||
|
||||
i += 1
|
||||
while i < end {
|
||||
let f = charFlags[Int(ptr[i])]
|
||||
if (f & 1) != 0 { break }
|
||||
sentences &+= UInt64((f >> 3) & 1)
|
||||
hasDigit |= UInt64((f >> 1) & 1)
|
||||
i += 1
|
||||
}
|
||||
|
||||
let wordLen = i - wordStart
|
||||
words &+= 1
|
||||
numbers &+= hasDigit
|
||||
|
||||
if wordLen <= 14 {
|
||||
forbidden &+= lookup.check(ptr + wordStart, wordLen)
|
||||
}
|
||||
}
|
||||
|
||||
return Stats(words: words, capitalized: capitalized, sentences: sentences, numbers: numbers, forbidden: forbidden)
|
||||
}
|
||||
|
||||
func processFile(_ path: String) -> Stats {
|
||||
let fd = open(path, O_RDONLY)
|
||||
guard fd >= 0 else { return Stats() }
|
||||
defer { close(fd) }
|
||||
|
||||
var st = stat()
|
||||
guard fstat(fd, &st) == 0 else { return Stats() }
|
||||
let size = Int(st.st_size)
|
||||
guard size > 0 else { return Stats() }
|
||||
|
||||
guard let mapped = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0), mapped != MAP_FAILED else {
|
||||
return Stats()
|
||||
}
|
||||
defer { munmap(mapped, size) }
|
||||
|
||||
let ptr = mapped.assumingMemoryBound(to: UInt8.self)
|
||||
return analyzeBuffer(ptr, size)
|
||||
}
|
||||
|
||||
func main() {
|
||||
let args = CommandLine.arguments
|
||||
guard args.count > 1 else {
|
||||
print("Usage: \(args[0]) <file1> <file2> ... <fileN>")
|
||||
return
|
||||
}
|
||||
|
||||
let files = Array(args.dropFirst())
|
||||
let fileCount = files.count
|
||||
|
||||
let resultsPtr = UnsafeMutablePointer<Stats>.allocate(capacity: fileCount)
|
||||
resultsPtr.initialize(repeating: Stats(), count: fileCount)
|
||||
defer { resultsPtr.deallocate() }
|
||||
|
||||
DispatchQueue.concurrentPerform(iterations: fileCount) { idx in
|
||||
resultsPtr[idx] = processFile(files[idx])
|
||||
}
|
||||
|
||||
var total = Stats()
|
||||
for i in 0..<fileCount {
|
||||
let r = resultsPtr[i]
|
||||
total.words &+= r.words
|
||||
total.capitalized &+= r.capitalized
|
||||
total.sentences &+= r.sentences
|
||||
total.numbers &+= r.numbers
|
||||
total.forbidden &+= r.forbidden
|
||||
}
|
||||
|
||||
let capitalizedPct = total.words > 0 ? Double(total.capitalized) / Double(total.words) * 100.0 : 0.0
|
||||
let forbiddenPct = total.words > 0 ? Double(total.forbidden) / Double(total.words) * 100.0 : 0.0
|
||||
let wordsPerSentence = total.sentences > 0 ? Double(total.words) / Double(total.sentences) : 0.0
|
||||
|
||||
print("")
|
||||
print("Total Words: \(total.words)")
|
||||
print("Total Capitalized words: \(total.capitalized)")
|
||||
print("Total Sentences: \(total.sentences)")
|
||||
print("Total Numbers: \(total.numbers)")
|
||||
print("Total Forbidden words: \(total.forbidden)")
|
||||
print(String(format: "Capitalized percentage: %.6f%%", capitalizedPct))
|
||||
print(String(format: "Forbidden percentage: %.6f%%", forbiddenPct))
|
||||
print(String(format: "Word count per sentence: %.6f", wordsPerSentence))
|
||||
print("Total files read: \(fileCount)")
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user