add an allocation counting integration test (#252)
Motivation: In Swift, it's really easy to accidentally add allocations yet the number of allocations is really important for performance. Therefore it's useful to have an integration test that counts the number of allocations and compares them to a reference (and fails if the number of allocations increases). Modifications: - add an allocation counter - add a HTTP1 client/server example that is run with the allocation counter intrumentation Result: We should now be able to track the number of allocations.
This commit is contained in:
parent
224120c175
commit
c84b19a399
|
@ -72,6 +72,7 @@ while getopts "f:" opt; do
|
|||
esac
|
||||
done
|
||||
|
||||
exec 3>&1 4>&2 # copy stdout/err to fd 3/4 to we can output control messages
|
||||
cnt_ok=0
|
||||
cnt_fail=0
|
||||
for f in tests_*; do
|
||||
|
|
|
@ -36,3 +36,31 @@ function assert_equal_files() {
|
|||
fail "file '$1' not equal to '$2'"
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_less_than() {
|
||||
if [[ ! "$1" -lt "$2" ]]; then
|
||||
fail "assertion '$1' < '$2' failed"
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_less_than_or_equal() {
|
||||
if [[ ! "$1" -le "$2" ]]; then
|
||||
fail "assertion '$1' <= '$2' failed"
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_greater_than() {
|
||||
if [[ ! "$1" -gt "$2" ]]; then
|
||||
fail "assertion '$1' > '$2' failed"
|
||||
fi
|
||||
}
|
||||
|
||||
function assert_greater_than_or_equal() {
|
||||
if [[ ! "$1" -ge "$2" ]]; then
|
||||
fail "assertion '$1' >= '$2' failed"
|
||||
fi
|
||||
}
|
||||
|
||||
function warn() {
|
||||
echo >&4 "warning: $*"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftNIO open source project
|
||||
##
|
||||
## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
|
@ -0,0 +1,74 @@
|
|||
#!/bin/bash
|
||||
##===----------------------------------------------------------------------===##
|
||||
##
|
||||
## This source file is part of the SwiftNIO open source project
|
||||
##
|
||||
## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
## Licensed under Apache License v2.0
|
||||
##
|
||||
## See LICENSE.txt for license information
|
||||
## See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
##
|
||||
## SPDX-License-Identifier: Apache-2.0
|
||||
##
|
||||
##===----------------------------------------------------------------------===##
|
||||
|
||||
source defines.sh
|
||||
|
||||
set -eu
|
||||
swift_bin=swift
|
||||
|
||||
cp -R "test_01_resources/template"/* "$tmp/"
|
||||
nio_root="$PWD/../.."
|
||||
|
||||
(
|
||||
cd "$tmp"
|
||||
|
||||
function make_git_commit_all() {
|
||||
git init
|
||||
git config --local user.email does@really-not.matter
|
||||
git config --local user.name 'Does Not Matter'
|
||||
git add .
|
||||
git commit -m 'everything'
|
||||
}
|
||||
|
||||
cd HookedFree
|
||||
make_git_commit_all
|
||||
cd ..
|
||||
|
||||
cd AtomicCounter
|
||||
make_git_commit_all
|
||||
cd ..
|
||||
|
||||
mkdir swift-nio
|
||||
cd swift-nio
|
||||
cat > Package.swift <<"EOF"
|
||||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(name: "swift-nio")
|
||||
EOF
|
||||
make_git_commit_all
|
||||
cd ..
|
||||
|
||||
"$swift_bin" package edit --path "$nio_root" swift-nio
|
||||
"$swift_bin" run -c release > "$tmp/output"
|
||||
)
|
||||
|
||||
for test in 1000_reqs_1_conn; do
|
||||
allocs=$(grep "$test:" "$tmp/output" | cut -d: -f2 | sed 's/ //g')
|
||||
max_allowed_env_name="MAX_ALLOCS_ALLOWED_$test"
|
||||
|
||||
assert_greater_than "$allocs" 1000
|
||||
if [[ -z "${!max_allowed_env_name+x}" ]]; then
|
||||
if [[ -z "${!max_allowed_env_name+x}" ]]; then
|
||||
warn "no reference number of allocations set (set to \$$max_allowed_env_name)"
|
||||
warn "to set current number:"
|
||||
warn " export $max_allowed_env_name=$allocs"
|
||||
fi
|
||||
else
|
||||
assert_less_than_or_equal "$allocs" "${!max_allowed_env_name}"
|
||||
fi
|
||||
done
|
|
@ -0,0 +1,36 @@
|
|||
# Allocation Counting Test
|
||||
|
||||
This briefly describes how the allocation counting test works.
|
||||
|
||||
## How does it work?
|
||||
|
||||
Basically it's the world's cheapest allocation counter, actually it is a memory `free` counter rather than a `malloc` counter. Why does it count `free` and not `malloc`? Because it's much easier. A correct implementation of `malloc` without relying on the system's `malloc` is non-trivial, an implementation of `free` however is trivial: just do nothing. Sure, you wouldn't want to run a real-world program with a free function that doesn't free the memory as you would run out of memory really quickly. For a short benchmark however it doesn't matter. The only thing that the hooked `free` function does is incrementing an atomic integer variable (representing the number of allocations) than can be read out elsewhere later.
|
||||
|
||||
### How is the free function hooked?
|
||||
|
||||
Usually in UNIX it's enough to just define a function
|
||||
|
||||
```C
|
||||
void free(void *ptr) { ... }
|
||||
```
|
||||
|
||||
in the main binary and all modules will use this `free` function instead of the real one from the `libc`. For Linux, this is exactly what we're doing, the `bootstrap` binary defines such a `free` function in its `main.c`. On Darwin (macOS/iOS/...) however that is not the case and you need to use [dyld's interpose feature](https://books.google.co.uk/books?id=K8vUkpOXhN4C&lpg=PA73&ots=OMjhRWWwUu&dq=dyld%20interpose&pg=PA73#v=onepage&q=dyld%20interpose&f=false). The odd thing is that dyld's interposing _only_ works if it's in a `.dylib` and not from a binary's main executable. Therefore we need to build a slightly strange SwiftPM package:
|
||||
|
||||
- `bootstrap`: The main executable's main module (written in C) so we can hook the `free` function on Linux.
|
||||
- `BootstrapSwift`: A SwiftPM module (written in Swift) called in from `bootstrap` which implements the actual SwiftNIO benchmark (and therefore depends on the `NIO` module).
|
||||
- `HookedFree`: A separate SwiftPM package that builds a shared library (`.so` on Linux, `.dylib` on Darwin) which contains the `replacement_free` function which just increments an atomic integer representing the number of allocations. On Darwin, we use `DYLD_INTERPOSE` in this module, interposing libc's `free` with our `replacement_free`. This needs to be a separate SwiftPM package as otherwise its code would just live inside of the `bootstrap` executable and the dyld interposing feature wouldn't work.
|
||||
- `AtomicCounter`: SwiftPM package (written in C) that implements the atomic counters. It needs to be a separate package as both `BoostrapSwift` (to read the allocation counter) as well as `HookedFree` (to increment the allocation counter) depend on it.
|
||||
|
||||
## What benchmark is run?
|
||||
|
||||
We run a single TCP connection over which 1000 HTTP requests are made by a client written in NIO, responded to by a server also written in NIO. We re-run the benchmark 10 times and return the lowest number of allocations that has been made.
|
||||
|
||||
## Why do I have to set a baseline?
|
||||
|
||||
By default this test should always succeed as it doesn't actually compare the number of allocations to a certain number. The reason is that this number varies ever so slightly between operating systems and Swift versions. At the time of writing on macOS we got roughly 326k allocations and on Linux 322k allocations for 1000 HTTP requests & responses. To set a baseline simply run
|
||||
|
||||
```bash
|
||||
export MAX_ALLOCS_ALLOWED_1000_reqs_1_conn=327000
|
||||
```
|
||||
|
||||
or similar to set the maximum number of allocations allowed. If the benchmark exceeds these allocations the test will fail.
|
|
@ -0,0 +1,30 @@
|
|||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "AtomicCounter",
|
||||
products: [
|
||||
.library(name: "AtomicCounter", targets: ["AtomicCounter"]),
|
||||
],
|
||||
dependencies: [ ],
|
||||
targets: [
|
||||
.target(
|
||||
name: "AtomicCounter",
|
||||
dependencies: []),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef ATOMIC_COUNTER
|
||||
|
||||
void inc_free_counter(void);
|
||||
void reset_free_counter(void);
|
||||
long read_free_counter(void);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define MAKE_COUNTER(name) /*
|
||||
*/ _Atomic long g_ ## name ## _counter = ATOMIC_VAR_INIT(0); /*
|
||||
*/ void inc_ ## name ## _counter(void) { /*
|
||||
*/ atomic_fetch_add_explicit(&g_ ## name ## _counter, 1, memory_order_relaxed); /*
|
||||
*/ } /*
|
||||
*/ /*
|
||||
*/ void reset_ ## name ## _counter(void) { /*
|
||||
*/ atomic_store_explicit(&g_ ## name ## _counter, 0, memory_order_relaxed); /*
|
||||
*/ } /*
|
||||
*/ /*
|
||||
*/ long read_ ## name ## _counter(void) { /*
|
||||
*/ return atomic_load_explicit(&g_ ## name ## _counter, memory_order_relaxed); /*
|
||||
*/ }
|
||||
|
||||
MAKE_COUNTER(free)
|
|
@ -0,0 +1,30 @@
|
|||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "HookedFree",
|
||||
products: [
|
||||
.library(name: "HookedFree", type: .dynamic, targets: ["HookedFree"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "../AtomicCounter/", .branch("master")),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "HookedFree", dependencies: ["AtomicCounter"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef HOOKED_FREE
|
||||
#define HOOKED_FREE
|
||||
|
||||
void replacement_free(void *ptr);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,31 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <hooked-free.h>
|
||||
#include <stdlib.h>
|
||||
#include <atomic-counter.h>
|
||||
|
||||
#define DYLD_INTERPOSE(_replacement,_replacee) \
|
||||
__attribute__((used)) static struct { const void *replacement; const void *replacee; } _interpose_##_replacee \
|
||||
__attribute__ ((section("__DATA,__interpose"))) = { (const void *)(unsigned long)&_replacement, (const void *)(unsigned long)&_replacee };
|
||||
|
||||
void replacement_free(void *ptr) {
|
||||
if (ptr) {
|
||||
inc_free_counter();
|
||||
}
|
||||
}
|
||||
|
||||
#if __APPLE__
|
||||
DYLD_INTERPOSE(replacement_free, free)
|
||||
#endif
|
|
@ -0,0 +1,20 @@
|
|||
// swift-tools-version:4.0
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "swift-malloc-info",
|
||||
dependencies: [
|
||||
.package(url: "HookedFree/", .branch("master")),
|
||||
.package(url: "swift-nio/", .branch("master")),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "SwiftBootstrap",
|
||||
dependencies: ["NIO", "NIOHTTP1"]),
|
||||
.target(
|
||||
name: "bootstrap",
|
||||
dependencies: ["SwiftBootstrap", "HookedFree"]),
|
||||
]
|
||||
)
|
|
@ -0,0 +1,157 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import NIO
|
||||
import NIOHTTP1
|
||||
import Foundation
|
||||
import AtomicCounter
|
||||
|
||||
private final class SimpleHTTPServer: ChannelInboundHandler {
|
||||
typealias InboundIn = HTTPServerRequestPart
|
||||
typealias OutboundOut = HTTPServerResponsePart
|
||||
|
||||
private let bodyLength = 100
|
||||
private let numberOfAdditionalHeaders = 3
|
||||
|
||||
private var responseHead: HTTPResponseHead {
|
||||
var head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok)
|
||||
head.headers.add(name: "Content-Length", value: "\(self.bodyLength)")
|
||||
for i in 0..<self.numberOfAdditionalHeaders {
|
||||
head.headers.add(name: "X-Random-Extra-Header", value: "\(i)")
|
||||
}
|
||||
return head
|
||||
}
|
||||
|
||||
private func responseBody(allocator: ByteBufferAllocator) -> ByteBuffer {
|
||||
var buffer = allocator.buffer(capacity: self.bodyLength)
|
||||
for i in 0..<self.bodyLength {
|
||||
buffer.write(integer: UInt8(i % Int(UInt8.max)))
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
|
||||
public func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
|
||||
if case .head(let req) = self.unwrapInboundIn(data), req.uri == "/allocation-test-1" {
|
||||
ctx.write(self.wrapOutboundOut(.head(self.responseHead)), promise: nil)
|
||||
ctx.write(self.wrapOutboundOut(.body(.byteBuffer(self.responseBody(allocator: ctx.channel.allocator)))), promise: nil)
|
||||
ctx.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("swift_main")
|
||||
public func swiftMain() -> Int {
|
||||
final class RepeatedRequests: ChannelInboundHandler {
|
||||
typealias InboundIn = HTTPClientResponsePart
|
||||
typealias OutboundOut = HTTPClientRequestPart
|
||||
|
||||
private let numberOfRequests: Int
|
||||
private var remainingNumberOfRequests: Int
|
||||
private let isDonePromise: EventLoopPromise<Int>
|
||||
static var requestHead: HTTPRequestHead {
|
||||
var head = HTTPRequestHead(version: HTTPVersion(major: 1, minor: 1), method: .GET, uri: "/allocation-test-1")
|
||||
head.headers.add(name: "Host", value: "foo-\(ObjectIdentifier(self)).com")
|
||||
return head
|
||||
}
|
||||
|
||||
init(numberOfRequests: Int, eventLoop: EventLoop) {
|
||||
self.remainingNumberOfRequests = numberOfRequests
|
||||
self.numberOfRequests = numberOfRequests
|
||||
self.isDonePromise = eventLoop.newPromise()
|
||||
}
|
||||
|
||||
func wait() throws -> Int {
|
||||
let reqs = try self.isDonePromise.futureResult.wait()
|
||||
precondition(reqs == self.numberOfRequests)
|
||||
return reqs
|
||||
}
|
||||
|
||||
func errorCaught(ctx: ChannelHandlerContext, error: Error) {
|
||||
ctx.channel.close(promise: nil)
|
||||
self.isDonePromise.fail(error: error)
|
||||
}
|
||||
|
||||
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
|
||||
let respPart = self.unwrapInboundIn(data)
|
||||
if case .end(nil) = respPart {
|
||||
if self.remainingNumberOfRequests <= 0 {
|
||||
ctx.channel.close().map { self.numberOfRequests - self.remainingNumberOfRequests }.cascade(promise: self.isDonePromise)
|
||||
} else {
|
||||
self.remainingNumberOfRequests -= 1
|
||||
ctx.write(self.wrapOutboundOut(.head(RepeatedRequests.requestHead)), promise: nil)
|
||||
ctx.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func measure(_ fn: () -> Int) -> [Int] {
|
||||
func measureOne(_ fn: () -> Int) -> Int {
|
||||
AtomicCounter.reset_free_counter()
|
||||
_ = fn()
|
||||
return AtomicCounter.read_free_counter()
|
||||
}
|
||||
|
||||
_ = measureOne(fn) /* pre-heat and throw away */
|
||||
var measurements: [Int] = []
|
||||
for _ in 0..<10 {
|
||||
measurements.append(measureOne(fn))
|
||||
}
|
||||
|
||||
return measurements
|
||||
}
|
||||
|
||||
func measureAndPrint(desc: String, fn: () -> Int) -> Void {
|
||||
print("\(desc): ", terminator: "")
|
||||
let measurements = measure(fn)
|
||||
print(measurements.min() ?? -1)
|
||||
}
|
||||
|
||||
measureAndPrint(desc: "1000_reqs_1_conn") {
|
||||
let group = MultiThreadedEventLoopGroup(numThreads: System.coreCount)
|
||||
defer {
|
||||
try! group.syncShutdownGracefully()
|
||||
}
|
||||
|
||||
let serverChannel = try! ServerBootstrap(group: group)
|
||||
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
|
||||
.childChannelInitializer { channel in
|
||||
channel.pipeline.configureHTTPServerPipeline(withPipeliningAssistance: true).then {
|
||||
channel.pipeline.add(handler: SimpleHTTPServer())
|
||||
}
|
||||
}.bind(host: "127.0.0.1", port: 0).wait()
|
||||
|
||||
defer {
|
||||
try! serverChannel.close().wait()
|
||||
}
|
||||
|
||||
|
||||
let repeatedRequestsHandler = RepeatedRequests(numberOfRequests: 1000, eventLoop: group.next())
|
||||
|
||||
let clientChannel = try! ClientBootstrap(group: group)
|
||||
.channelInitializer { channel in
|
||||
channel.pipeline.addHTTPClientHandlers().then {
|
||||
channel.pipeline.add(handler: repeatedRequestsHandler)
|
||||
}
|
||||
}
|
||||
.connect(to: serverChannel.localAddress!)
|
||||
.wait()
|
||||
|
||||
clientChannel.write(NIOAny(HTTPClientRequestPart.head(RepeatedRequests.requestHead)), promise: nil)
|
||||
try! clientChannel.writeAndFlush(NIOAny(HTTPClientRequestPart.end(nil))).wait()
|
||||
return try! repeatedRequestsHandler.wait()
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the SwiftNIO open source project
|
||||
//
|
||||
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
|
||||
// Licensed under Apache License v2.0
|
||||
//
|
||||
// See LICENSE.txt for license information
|
||||
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <dlfcn.h>
|
||||
#include <atomic-counter.h>
|
||||
#include <hooked-free.h>
|
||||
|
||||
#if !__APPLE__
|
||||
void free(void *ptr) {
|
||||
replacement_free(ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
void swift_main(void);
|
||||
|
||||
int main() {
|
||||
swift_main();
|
||||
}
|
Loading…
Reference in New Issue