Initial commit of snmalloc
History squashed from internal development. Internal history has commit hash: e27a0e485c44a5003a802de2661ce3b21e120316
This commit is contained in:
110
.clang-format
Normal file
110
.clang-format
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
---
|
||||||
|
Language: Cpp
|
||||||
|
# BasedOnStyle: LLVM
|
||||||
|
AccessModifierOffset: -2
|
||||||
|
AlignAfterOpenBracket: AlwaysBreak
|
||||||
|
AlignConsecutiveAssignments: false
|
||||||
|
AlignConsecutiveDeclarations: false
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignOperands: false
|
||||||
|
AlignTrailingComments: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: true
|
||||||
|
AllowShortBlocksOnASingleLine: false
|
||||||
|
AllowShortCaseLabelsOnASingleLine: false
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AlwaysBreakAfterDefinitionReturnType: None
|
||||||
|
AlwaysBreakAfterReturnType: None
|
||||||
|
AlwaysBreakBeforeMultilineStrings: true
|
||||||
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BraceWrapping:
|
||||||
|
AfterClass: true
|
||||||
|
AfterControlStatement: true
|
||||||
|
AfterEnum: true
|
||||||
|
AfterFunction: true
|
||||||
|
AfterNamespace: true
|
||||||
|
AfterObjCDeclaration: true
|
||||||
|
AfterStruct: true
|
||||||
|
AfterUnion: true
|
||||||
|
AfterExternBlock: true
|
||||||
|
BeforeCatch: true
|
||||||
|
BeforeElse: true
|
||||||
|
IndentBraces: false
|
||||||
|
SplitEmptyFunction: false
|
||||||
|
SplitEmptyRecord: false
|
||||||
|
SplitEmptyNamespace: false
|
||||||
|
BreakBeforeBinaryOperators: None
|
||||||
|
BreakBeforeBraces: Custom
|
||||||
|
BreakBeforeInheritanceComma: false
|
||||||
|
BreakBeforeTernaryOperators: false
|
||||||
|
BreakConstructorInitializersBeforeComma: false
|
||||||
|
BreakConstructorInitializers: BeforeColon
|
||||||
|
BreakAfterJavaFieldAnnotations: false
|
||||||
|
BreakStringLiterals: true
|
||||||
|
ColumnLimit: 80
|
||||||
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
|
CompactNamespaces: false
|
||||||
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
|
ConstructorInitializerIndentWidth: 0
|
||||||
|
ContinuationIndentWidth: 2
|
||||||
|
Cpp11BracedListStyle: true
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
DisableFormat: false
|
||||||
|
ExperimentalAutoDetectBinPacking: false
|
||||||
|
FixNamespaceComments: false
|
||||||
|
ForEachMacros:
|
||||||
|
- Q_FOREACH
|
||||||
|
- BOOST_FOREACH
|
||||||
|
IncludeBlocks: Regroup
|
||||||
|
IncludeCategories:
|
||||||
|
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
|
||||||
|
Priority: 2
|
||||||
|
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||||
|
Priority: 3
|
||||||
|
- Regex: '.*'
|
||||||
|
Priority: 1
|
||||||
|
IncludeIsMainRegex: '(Test)?$'
|
||||||
|
IndentCaseLabels: true
|
||||||
|
IndentPPDirectives: AfterHash
|
||||||
|
IndentWidth: 2
|
||||||
|
IndentWrappedFunctionNames: false
|
||||||
|
JavaScriptQuotes: Leave
|
||||||
|
JavaScriptWrapImports: true
|
||||||
|
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||||
|
MacroBlockBegin: ''
|
||||||
|
MacroBlockEnd: ''
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
NamespaceIndentation: All
|
||||||
|
ObjCBlockIndentWidth: 2
|
||||||
|
ObjCSpaceAfterProperty: false
|
||||||
|
ObjCSpaceBeforeProtocolList: true
|
||||||
|
PenaltyBreakAssignment: 2
|
||||||
|
PenaltyBreakBeforeFirstCallParameter: 19
|
||||||
|
PenaltyBreakComment: 300
|
||||||
|
PenaltyBreakFirstLessLess: 120
|
||||||
|
PenaltyBreakString: 1000
|
||||||
|
PenaltyExcessCharacter: 1000000
|
||||||
|
PenaltyReturnTypeOnItsOwnLine: 60
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: true
|
||||||
|
SortUsingDeclarations: true
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpaceAfterTemplateKeyword: false
|
||||||
|
SpaceBeforeAssignmentOperators: true
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpaceInEmptyParentheses: false
|
||||||
|
SpacesBeforeTrailingComments: 1
|
||||||
|
SpacesInAngles: false
|
||||||
|
SpacesInContainerLiterals: false
|
||||||
|
SpacesInCStyleCastParentheses: false
|
||||||
|
SpacesInParentheses: false
|
||||||
|
SpacesInSquareBrackets: false
|
||||||
|
Standard: Cpp11
|
||||||
|
TabWidth: 2
|
||||||
|
UseTab: Never
|
||||||
|
...
|
||||||
|
|
||||||
333
.gitignore
vendored
333
.gitignore
vendored
@@ -1,330 +1,5 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
build*/
|
||||||
## files generated by popular Visual Studio add-ons.
|
CMakeFiles/
|
||||||
##
|
.vscode/
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015/2017 cache/options directory
|
|
||||||
.vs/
|
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# Visual Studio 2017 auto generated files
|
|
||||||
Generated\ Files/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# Benchmark Results
|
|
||||||
BenchmarkDotNet.Artifacts/
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
**/Properties/launchSettings.json
|
|
||||||
|
|
||||||
# StyleCop
|
|
||||||
StyleCopReport.xml
|
|
||||||
|
|
||||||
# Files built by Visual Studio
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.iobj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.ipdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# Visual Studio Trace Files
|
|
||||||
*.e2e
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# AxoCover is a Code Coverage Tool
|
|
||||||
.axoCover/*
|
|
||||||
!.axoCover/settings.json
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/[Pp]ackages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/[Pp]ackages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/[Pp]ackages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
*.appx
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
*~
|
||||||
*.dbmdl
|
*.sw?
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Including strong name files can present a security risk
|
|
||||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
|
||||||
#*.snk
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
ServiceFabricBackup/
|
|
||||||
*.rptproj.bak
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
*.rptproj.rsuser
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Tabs Studio
|
|
||||||
*.tss
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
||||||
# OpenCover UI analysis results
|
|
||||||
OpenCover/
|
|
||||||
|
|
||||||
# Azure Stream Analytics local run output
|
|
||||||
ASALocalRun/
|
|
||||||
|
|
||||||
# MSBuild Binary and Structured Log
|
|
||||||
*.binlog
|
|
||||||
|
|
||||||
# NVidia Nsight GPU debugger configuration file
|
|
||||||
*.nvuser
|
|
||||||
|
|
||||||
# MFractors (Xamarin productivity tool) working folder
|
|
||||||
.mfractor/
|
|
||||||
|
|||||||
92
.vsts-ci.yml
Normal file
92
.vsts-ci.yml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
resources:
|
||||||
|
- repo: self
|
||||||
|
|
||||||
|
phases:
|
||||||
|
- phase: Linux
|
||||||
|
queue:
|
||||||
|
name: 'Hosted Ubuntu 1604'
|
||||||
|
parallel: 2
|
||||||
|
matrix:
|
||||||
|
Debug:
|
||||||
|
BuildType: Debug
|
||||||
|
Release:
|
||||||
|
BuildType: Release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- script: |
|
||||||
|
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y ninja-build libc++-dev libc++abi-dev libc++abi1 libstdc++-7-dev
|
||||||
|
# sudo apt-get install clang-6.0 clang++-6.0
|
||||||
|
|
||||||
|
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-6.0 100
|
||||||
|
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/clang 100
|
||||||
|
sudo update-alternatives --set cc /usr/bin/clang
|
||||||
|
|
||||||
|
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-6.0 100
|
||||||
|
sudo update-alternatives --install /usr/bin/c++ c++ /usr/bin/clang++ 100
|
||||||
|
sudo update-alternatives --set c++ /usr/bin/clang++
|
||||||
|
|
||||||
|
displayName: 'Install Build Dependencies'
|
||||||
|
|
||||||
|
- task: CMake@1
|
||||||
|
displayName: 'CMake .. -GNinja -DCMAKE_BUILD_TYPE=$(BuildType) -DCMAKE_CXX_FLAGS="-stdlib=libstdc++ -std=c++17"'
|
||||||
|
inputs:
|
||||||
|
cmakeArgs: '.. -GNinja -DCMAKE_BUILD_TYPE=$(BuildType) -DCMAKE_CXX_FLAGS="-stdlib=libstdc++ -std=c++17"'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
ninja
|
||||||
|
ctest -j 4 --output-on-failure
|
||||||
|
|
||||||
|
workingDirectory: build
|
||||||
|
failOnStderr: true
|
||||||
|
displayName: 'Compile & Test'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
sudo cp libsnmallocshim.so /usr/local/lib/
|
||||||
|
ninja clean
|
||||||
|
LD_PRELOAD=/usr/local/lib/libsnmallocshim.so ninja
|
||||||
|
workingDirectory: build
|
||||||
|
failOnStderr: true
|
||||||
|
displayName: 'LD_PRELOAD Compile'
|
||||||
|
|
||||||
|
- phase: Windows
|
||||||
|
queue:
|
||||||
|
name: 'Hosted VS2017'
|
||||||
|
parallel: 2
|
||||||
|
matrix:
|
||||||
|
Debug:
|
||||||
|
BuildType: Debug
|
||||||
|
Release:
|
||||||
|
BuildType: Release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: CMake@1
|
||||||
|
displayName: 'CMake .. -G"Visual Studio 15 2017 Win64"'
|
||||||
|
inputs:
|
||||||
|
cmakeArgs: '.. -G"Visual Studio 15 2017 Win64"'
|
||||||
|
|
||||||
|
- task: MSBuild@1
|
||||||
|
displayName: 'Build solution build/snmalloc.sln'
|
||||||
|
inputs:
|
||||||
|
solution: build/snmalloc.sln
|
||||||
|
msbuildArguments: '/m /p:Configuration=$(BuildType)'
|
||||||
|
|
||||||
|
- script: 'ctest -j 4 --interactive-debug-mode 0 --output-on-failure'
|
||||||
|
workingDirectory: build
|
||||||
|
displayName: 'Run Ctest'
|
||||||
|
|
||||||
|
- phase: Format
|
||||||
|
queue:
|
||||||
|
name: 'Hosted Ubuntu 1604'
|
||||||
|
steps:
|
||||||
|
- script: |
|
||||||
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||||
|
sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-6.0 main"
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang-format-6.0
|
||||||
|
sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-6.0 100
|
||||||
|
|
||||||
|
bash check-format.sh
|
||||||
|
|
||||||
|
displayName: 'Check Format'
|
||||||
107
CMakeLists.txt
Normal file
107
CMakeLists.txt
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8)
|
||||||
|
project(snmalloc C CXX)
|
||||||
|
|
||||||
|
option(USE_SNMALLOC_STATS "Track allocation stats" OFF)
|
||||||
|
option(USE_MEASURE "Measure performance with histograms" OFF)
|
||||||
|
option(USE_SBRK "Use sbrk instead of mmap" OFF)
|
||||||
|
|
||||||
|
macro(subdirlist result curdir)
|
||||||
|
file(GLOB children LIST_DIRECTORIES true RELATIVE ${curdir} ${curdir}/*)
|
||||||
|
set(dirlist "")
|
||||||
|
foreach(child ${children})
|
||||||
|
if(IS_DIRECTORY ${curdir}/${child})
|
||||||
|
list(APPEND dirlist ${child})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
set(${result} ${dirlist})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
macro(linklibs project)
|
||||||
|
if(NOT MSVC)
|
||||||
|
target_link_libraries(${project} ${CMAKE_THREAD_LIBS_INIT})
|
||||||
|
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||||
|
target_link_libraries(${project} atomic)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_compile_options(/WX /W4 /wd4127 /wd4324 /wd4201 /std:c++latest)
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
|
||||||
|
else()
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
add_compile_options(-mcx16 -march=native -Wall -Wextra -Werror -g)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
if(USE_SNMALLOC_STATS)
|
||||||
|
add_definitions(-DUSE_SNMALLOC_STATS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_MEASURE)
|
||||||
|
add_definitions(-DUSE_MEASURE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(USE_SBRK)
|
||||||
|
add_definitions(-DUSE_SBRK)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT MSVC)
|
||||||
|
add_library(snmallocshim SHARED src/override/malloc.cc)
|
||||||
|
target_link_libraries(snmallocshim -pthread)
|
||||||
|
target_include_directories(snmallocshim PRIVATE src)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
set(TESTDIR ${CMAKE_CURRENT_SOURCE_DIR}/src/test)
|
||||||
|
subdirlist(TEST_CATEGORIES ${TESTDIR})
|
||||||
|
foreach(TEST_CATEGORY ${TEST_CATEGORIES})
|
||||||
|
subdirlist(TESTS ${TESTDIR}/${TEST_CATEGORY})
|
||||||
|
foreach(TEST ${TESTS})
|
||||||
|
unset(SRC)
|
||||||
|
aux_source_directory(${TESTDIR}/${TEST_CATEGORY}/${TEST} SRC)
|
||||||
|
set(TESTNAME "${TEST_CATEGORY}-${TEST}")
|
||||||
|
add_executable(${TESTNAME} ${SRC} src/override/new.cc)
|
||||||
|
target_include_directories(${TESTNAME} PRIVATE src)
|
||||||
|
linklibs(${TESTNAME})
|
||||||
|
add_test(${TESTNAME} ${TESTNAME})
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
|
||||||
|
# The clang-format tool is installed under a variety of different names. Try
|
||||||
|
# to find a sensible one. Only look for 6.0 and 7.0 versions explicitly - we
|
||||||
|
# don't know whether our clang-format file will work with newer versions of the
|
||||||
|
# tool
|
||||||
|
set(CLANG_FORMAT_NAMES
|
||||||
|
clang-format-7.0
|
||||||
|
clang-format-6.0
|
||||||
|
clang-format70
|
||||||
|
clang-format60
|
||||||
|
clang-format)
|
||||||
|
|
||||||
|
# Loop over each of the possible names of clang-format and try to find one.
|
||||||
|
set(CLANG_FORMAT CLANG_FORMAT-NOTFOUND)
|
||||||
|
foreach (NAME IN ITEMS ${CLANG_FORMAT_NAMES})
|
||||||
|
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
|
||||||
|
find_program(CLANG_FORMAT ${NAME})
|
||||||
|
endif ()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# If we've found a clang-format tool, generate a target for it, otherwise emit
|
||||||
|
# a warning.
|
||||||
|
if (${CLANG_FORMAT} STREQUAL "CLANG_FORMAT-NOTFOUND")
|
||||||
|
message(WARNING "Not generating clangformat target, no clang-format tool found")
|
||||||
|
else ()
|
||||||
|
message(STATUS "Generating clangformat target using ${CLANG_FORMAT}")
|
||||||
|
file(GLOB_RECURSE ALL_SOURCE_FILES *.cc *.h *.hh)
|
||||||
|
add_custom_target(
|
||||||
|
clangformat
|
||||||
|
COMMAND ${CLANG_FORMAT}
|
||||||
|
-i
|
||||||
|
${ALL_SOURCE_FILES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
78
README.md
78
README.md
@@ -1,3 +1,81 @@
|
|||||||
|
# snmalloc
|
||||||
|
|
||||||
|
snmalloc is a research allocator. Its key design features are:
|
||||||
|
|
||||||
|
* Memory that is freed by the same thread that allocated it does not require any
|
||||||
|
synchronising operations.
|
||||||
|
* Freeing memory in a different thread to initially allocated it, does not take
|
||||||
|
any locks and instead uses a novel message passing scheme to return the
|
||||||
|
memory to the original allocator, where it is recycled.
|
||||||
|
* The allocator uses large ranges of pages to reduce the amount of meta-data
|
||||||
|
required.
|
||||||
|
|
||||||
|
# Building on Windows
|
||||||
|
|
||||||
|
The Windows build currently depends on Visual Studio 2017.
|
||||||
|
To build with Visual Studio:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -G "Visual Studio 15 2017 Win64" ..
|
||||||
|
cmake --build . --config Debug
|
||||||
|
cmake --build . --config Release
|
||||||
|
cmake --build . --config RelWithDebInfo
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also omit the last three steps and build from the IDE.
|
||||||
|
Visual Studio builds use a separate directory to keep the binaries for each
|
||||||
|
build configuration.
|
||||||
|
|
||||||
|
Alternatively, you can follow the steps in the next section to build with Ninja
|
||||||
|
using the Visual Studio compiler.
|
||||||
|
|
||||||
|
# Building on Linux or FreeBSD
|
||||||
|
Snmalloc has very few dependencies, CMake, Ninja, Clang 6.0 or later and a C++17
|
||||||
|
standard library.
|
||||||
|
Building with GCC is currently not supported because GCC lacks support for the
|
||||||
|
`selectany` attribute to specify variables in a COMDAT.
|
||||||
|
|
||||||
|
To build a debug configuration:
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
ninja
|
||||||
|
```
|
||||||
|
To build a release configuration:
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=Release
|
||||||
|
ninja
|
||||||
|
```
|
||||||
|
To build with optimizations on, but with debug information:
|
||||||
|
```
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -G Ninja .. -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||||
|
ninja
|
||||||
|
```
|
||||||
|
|
||||||
|
The build produces a binary `libsnmallocshim.so`. This file can be
|
||||||
|
`LD_PRELOAD`ed to use the allocator in place of the system allocator, for
|
||||||
|
example, you can run the build script using the snmalloc as the allocator for
|
||||||
|
your toolchain:
|
||||||
|
|
||||||
|
```
|
||||||
|
LD_PRELOAD=/usr/local/lib/libsnmallocshim.so ninja
|
||||||
|
```
|
||||||
|
|
||||||
|
# CMake Feature Flags
|
||||||
|
|
||||||
|
These can be added to your cmake command line.
|
||||||
|
|
||||||
|
```
|
||||||
|
-DUSE_SNMALLOC_STATS=ON // Track allocation stats
|
||||||
|
-DUSE_MEASURE=ON // Measure performance with histograms
|
||||||
|
```
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
|||||||
18
check-format.sh
Normal file
18
check-format.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
unformatted_files=""
|
||||||
|
for f in `find . -name *.h -o -name *.hh -o -name *.cc`; do
|
||||||
|
d=`clang-format -style=file $f | diff $f -`
|
||||||
|
if [ "$d" != "" ]; then
|
||||||
|
if [ "$unformatted_files" != "" ]; then
|
||||||
|
unformatted_files+=$'\n'
|
||||||
|
fi
|
||||||
|
unformatted_files+="$f"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$unformatted_files" != "" ]; then
|
||||||
|
echo "$unformatted_files"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
113
src/ds/aba.h
Normal file
113
src/ds/aba.h
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "bits.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<typename T, Construction c = RequiresInit>
|
||||||
|
class ABA
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
struct alignas(2 * sizeof(std::size_t)) Linked
|
||||||
|
{
|
||||||
|
T* ptr;
|
||||||
|
uintptr_t aba;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Independent
|
||||||
|
{
|
||||||
|
std::atomic<T*> ptr;
|
||||||
|
std::atomic<uintptr_t> aba;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
sizeof(Linked) == sizeof(Independent),
|
||||||
|
"Expecting identical struct sizes in union");
|
||||||
|
static_assert(
|
||||||
|
sizeof(Linked) == (2 * sizeof(std::size_t)),
|
||||||
|
"Expecting ABA to be the size of two pointers");
|
||||||
|
|
||||||
|
using Cmp = Linked;
|
||||||
|
#else
|
||||||
|
using Cmp = T*;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
union
|
||||||
|
{
|
||||||
|
alignas(2 * sizeof(std::size_t)) std::atomic<Linked> linked;
|
||||||
|
Independent independent;
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
std::atomic<T*> ptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
ABA()
|
||||||
|
{
|
||||||
|
if constexpr (c == RequiresInit)
|
||||||
|
init(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(T* x)
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
independent.ptr.store(x, std::memory_order_relaxed);
|
||||||
|
independent.aba.store(0, std::memory_order_relaxed);
|
||||||
|
#else
|
||||||
|
ptr.store(x, std::memory_order_relaxed);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
T* peek()
|
||||||
|
{
|
||||||
|
return independent.ptr.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cmp read()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
Cmp{independent.ptr.load(std::memory_order_relaxed),
|
||||||
|
independent.aba.load(std::memory_order_relaxed)};
|
||||||
|
#else
|
||||||
|
ptr.load(std::memory_order_relaxed);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static T* load(Cmp& from)
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
return from.ptr;
|
||||||
|
#else
|
||||||
|
return from;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compare_exchange(Cmp& expect, T* value)
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_IS_X86
|
||||||
|
# if defined(_MSC_VER) && defined(PLATFORM_BITS_64)
|
||||||
|
return _InterlockedCompareExchange128(
|
||||||
|
(volatile __int64*)&linked,
|
||||||
|
expect.aba + 1,
|
||||||
|
(__int64)value,
|
||||||
|
(__int64*)&expect);
|
||||||
|
# else
|
||||||
|
# if defined(__GNUC__) && !defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)
|
||||||
|
#error You must compile with -mcx16 to enable 16-bit atomic compare and swap.
|
||||||
|
# endif
|
||||||
|
Cmp xchg{value, expect.aba + 1};
|
||||||
|
|
||||||
|
return linked.compare_exchange_weak(
|
||||||
|
expect, xchg, std::memory_order_relaxed, std::memory_order_relaxed);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
return ptr.compare_exchange_weak(
|
||||||
|
expect, value, std::memory_order_relaxed, std::memory_order_relaxed);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
466
src/ds/bits.h
Normal file
466
src/ds/bits.h
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# include <immintrin.h>
|
||||||
|
# include <intrin.h>
|
||||||
|
# define ALWAYSINLINE __forceinline
|
||||||
|
# define NOINLINE __declspec(noinline)
|
||||||
|
# define HEADER_GLOBAL __declspec(selectany)
|
||||||
|
#else
|
||||||
|
# include <cpuid.h>
|
||||||
|
# include <emmintrin.h>
|
||||||
|
# define ALWAYSINLINE __attribute__((always_inline))
|
||||||
|
# define NOINLINE __attribute__((noinline))
|
||||||
|
# define HEADER_GLOBAL __attribute__((selectany))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__i386__) || defined(_M_IX86) || defined(_X86_) || \
|
||||||
|
defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) || \
|
||||||
|
defined(_M_AMD64)
|
||||||
|
# define PLATFORM_IS_X86
|
||||||
|
# if defined(__linux__) && !defined(OPEN_ENCLAVE)
|
||||||
|
# include <x86intrin.h>
|
||||||
|
# endif
|
||||||
|
# if defined(__amd64__) || defined(__x86_64__) || defined(_M_X64) || \
|
||||||
|
defined(_M_AMD64)
|
||||||
|
# define PLATFORM_BITS_64
|
||||||
|
# else
|
||||||
|
# define PLATFORM_BITS_32
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && defined(PLATFORM_BITS_32)
|
||||||
|
# include <intsafe.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef __has_builtin
|
||||||
|
# define __has_builtin(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define UNUSED(x) ((void)x)
|
||||||
|
|
||||||
|
// #define USE_LZCNT
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#ifdef pause
|
||||||
|
# undef pause
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
// Used to enable trivial constructors for
|
||||||
|
// class that zero init is sufficient.
|
||||||
|
// Supplying PreZeroed means the memory is pre-zeroed i.e. a global section
|
||||||
|
// RequiresInit is if the class needs to zero its fields.
|
||||||
|
enum Construction
|
||||||
|
{
|
||||||
|
PreZeroed,
|
||||||
|
RequiresInit
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace bits
|
||||||
|
{
|
||||||
|
static constexpr size_t BITS = sizeof(size_t) * 8;
|
||||||
|
|
||||||
|
static constexpr bool is64()
|
||||||
|
{
|
||||||
|
return BITS == 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t ADDRESS_BITS = is64() ? 48 : 32;
|
||||||
|
|
||||||
|
inline void pause()
|
||||||
|
{
|
||||||
|
#if defined(PLATFORM_IS_X86)
|
||||||
|
_mm_pause();
|
||||||
|
#else
|
||||||
|
# warning "Missing pause intrinsic"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t tick()
|
||||||
|
{
|
||||||
|
#if defined(PLATFORM_IS_X86)
|
||||||
|
# if defined(_MSC_VER)
|
||||||
|
return __rdtsc();
|
||||||
|
# elif defined(__clang__)
|
||||||
|
return __builtin_readcyclecounter();
|
||||||
|
# else
|
||||||
|
return __builtin_ia32_rdtsc();
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# error Define CPU tick for this platform
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t tickp()
|
||||||
|
{
|
||||||
|
#if defined(PLATFORM_IS_X86)
|
||||||
|
# if defined(_MSC_VER)
|
||||||
|
unsigned int aux;
|
||||||
|
return __rdtscp(&aux);
|
||||||
|
# else
|
||||||
|
unsigned aux;
|
||||||
|
return __builtin_ia32_rdtscp(&aux);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# error Define CPU tick for this platform
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void halt_out_of_order()
|
||||||
|
{
|
||||||
|
#if defined(PLATFORM_IS_X86)
|
||||||
|
# if defined(_MSC_VER)
|
||||||
|
int cpu_info[4];
|
||||||
|
__cpuid(cpu_info, 0);
|
||||||
|
# else
|
||||||
|
unsigned int eax, ebx, ecx, edx;
|
||||||
|
__get_cpuid(0, &eax, &ebx, &ecx, &edx);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
# error Define CPU benchmark start time for this platform
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t benchmark_time_start()
|
||||||
|
{
|
||||||
|
halt_out_of_order();
|
||||||
|
return tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint64_t benchmark_time_end()
|
||||||
|
{
|
||||||
|
uint64_t t = tickp();
|
||||||
|
halt_out_of_order();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t clz(size_t x)
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# ifdef USE_LZCNT
|
||||||
|
# ifdef PLATFORM_BITS_64
|
||||||
|
return __lzcnt64(x);
|
||||||
|
# else
|
||||||
|
return __lzcnt((uint32_t)x);
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
unsigned long index;
|
||||||
|
|
||||||
|
# ifdef PLATFORM_BITS_64
|
||||||
|
_BitScanReverse64(&index, x);
|
||||||
|
# else
|
||||||
|
_BitScanReverse(&index, (unsigned long)x);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
return BITS - index - 1;
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
return (size_t)__builtin_clzl(x);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr size_t rotr_const(size_t x, size_t n)
|
||||||
|
{
|
||||||
|
size_t nn = n & (BITS - 1);
|
||||||
|
return (x >> nn) | (x << (((size_t) - (int)nn) & (BITS - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr size_t rotl_const(size_t x, size_t n)
|
||||||
|
{
|
||||||
|
size_t nn = n & (BITS - 1);
|
||||||
|
return (x << nn) | (x >> (((size_t) - (int)nn) & (BITS - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t rotr(size_t x, size_t n)
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# ifdef PLATFORM_BITS_64
|
||||||
|
return _rotr64(x, (int)n);
|
||||||
|
# else
|
||||||
|
return _rotr((uint32_t)x, (int)n);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
return rotr_const(x, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t rotl(size_t x, size_t n)
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# ifdef PLATFORM_BITS_64
|
||||||
|
return _rotl64(x, (int)n);
|
||||||
|
# else
|
||||||
|
return _rotl((uint32_t)x, (int)n);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
return rotl_const(x, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t clz_const(size_t x)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
for (int i = BITS - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
size_t mask = (size_t)1 << i;
|
||||||
|
|
||||||
|
if ((x & mask) == mask)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t ctz(size_t x)
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
# ifdef PLATFORM_BITS_64
|
||||||
|
return _tzcnt_u64(x);
|
||||||
|
# else
|
||||||
|
return _tzcnt_u32((uint32_t)x);
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
return (size_t)__builtin_ctzl(x);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t ctz_const(size_t x)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < BITS; i++)
|
||||||
|
{
|
||||||
|
size_t mask = (size_t)1 << i;
|
||||||
|
|
||||||
|
if ((x & mask) == mask)
|
||||||
|
return n;
|
||||||
|
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t umul(size_t x, size_t y, bool& overflow)
|
||||||
|
{
|
||||||
|
#if __has_builtin(__builtin_mul_overflow)
|
||||||
|
size_t prod;
|
||||||
|
overflow = __builtin_mul_overflow(x, y, &prod);
|
||||||
|
return prod;
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
# if defined(PLATFORM_BITS_64)
|
||||||
|
size_t high_prod;
|
||||||
|
size_t prod = _umul128(x, y, &high_prod);
|
||||||
|
overflow = high_prod != 0;
|
||||||
|
return prod;
|
||||||
|
# else
|
||||||
|
size_t prod;
|
||||||
|
overflow = S_OK == UIntMult(x, y, &prod);
|
||||||
|
return prod;
|
||||||
|
# endif
|
||||||
|
#else
|
||||||
|
size_t prod = x * y;
|
||||||
|
return y && (x > ((size_t)-1 / y));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t next_pow2(size_t x)
|
||||||
|
{
|
||||||
|
// Correct for numbers [0..MAX_SIZE >> 1).
|
||||||
|
// Returns 1 for x > (MAX_SIZE >> 1).
|
||||||
|
if (x <= 2)
|
||||||
|
return x;
|
||||||
|
|
||||||
|
return (size_t)1 << (BITS - clz(x - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t next_pow2_bits(size_t x)
|
||||||
|
{
|
||||||
|
// Correct for numbers [1..MAX_SIZE].
|
||||||
|
// Returns 64 for 0. Approximately 2 cycles.
|
||||||
|
return BITS - clz(x - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t next_pow2_const(size_t x)
|
||||||
|
{
|
||||||
|
if (x <= 2)
|
||||||
|
return x;
|
||||||
|
|
||||||
|
return (size_t)1 << (BITS - clz_const(x - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_t next_pow2_bits_const(size_t x)
|
||||||
|
{
|
||||||
|
return BITS - clz_const(x - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static size_t hash(void* p)
|
||||||
|
{
|
||||||
|
size_t x = (size_t)p;
|
||||||
|
|
||||||
|
if (is64())
|
||||||
|
{
|
||||||
|
x = ~x + (x << 21);
|
||||||
|
x = x ^ (x >> 24);
|
||||||
|
x = (x + (x << 3)) + (x << 8);
|
||||||
|
x = x ^ (x >> 14);
|
||||||
|
x = (x + (x << 2)) + (x << 4);
|
||||||
|
x = x ^ (x >> 28);
|
||||||
|
x = x + (x << 31);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x = ~x + (x << 15);
|
||||||
|
x = x ^ (x >> 12);
|
||||||
|
x = x + (x << 2);
|
||||||
|
x = x ^ (x >> 4);
|
||||||
|
x = (x + (x << 3)) + (x << 11);
|
||||||
|
x = x ^ (x >> 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t align_down(size_t value, size_t alignment)
|
||||||
|
{
|
||||||
|
assert(next_pow2(alignment) == alignment);
|
||||||
|
|
||||||
|
size_t align_1 = alignment - 1;
|
||||||
|
value &= ~align_1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t align_up(size_t value, size_t alignment)
|
||||||
|
{
|
||||||
|
assert(next_pow2(alignment) == alignment);
|
||||||
|
|
||||||
|
size_t align_1 = alignment - 1;
|
||||||
|
value += align_1;
|
||||||
|
value &= ~align_1;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t alignment>
|
||||||
|
static inline bool is_aligned_block(void* p, size_t size)
|
||||||
|
{
|
||||||
|
assert(next_pow2(alignment) == alignment);
|
||||||
|
|
||||||
|
return (((size_t)p | size) & (alignment - 1)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
constexpr T inc_mod(T v, T mod)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_integral<T>::value, "inc_mod can only be used on integers");
|
||||||
|
|
||||||
|
using S = std::make_signed_t<T>;
|
||||||
|
constexpr S shift = (sizeof(S) * 8) - 1;
|
||||||
|
|
||||||
|
S a = (S)(v + 1);
|
||||||
|
S b = (S)(mod - a - 1);
|
||||||
|
return a & ~(b >> shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************************************
|
||||||
|
*
|
||||||
|
* Map large range of strictly positive integers
|
||||||
|
* into an exponent and mantissa pair.
|
||||||
|
*
|
||||||
|
* The reverse mapping is given as:
|
||||||
|
*
|
||||||
|
* e | m | value
|
||||||
|
* ---------------------------------
|
||||||
|
* 0 | x1 ... xm | 0..00 x1 .. xm
|
||||||
|
* 1 | x1 ... xm | 0..01 x1 .. xm
|
||||||
|
* 2 | x1 ... xm | 0..1 x1 .. xm 0
|
||||||
|
* 3 | x1 ... xm | 0.1 x1 .. xm 00
|
||||||
|
*
|
||||||
|
* The forward mapping maps a value to the
|
||||||
|
* smallest exponent and mantissa with a
|
||||||
|
* reverse mapping not less than the value.
|
||||||
|
*
|
||||||
|
* Does not work for value=0.
|
||||||
|
***********************************************/
|
||||||
|
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||||
|
static size_t to_exp_mant(size_t value)
|
||||||
|
{
|
||||||
|
value += ((size_t)1 << (LOW_BITS)) - 1;
|
||||||
|
value >>= LOW_BITS;
|
||||||
|
|
||||||
|
if (MANTISSA_BITS > 0)
|
||||||
|
{
|
||||||
|
size_t LEADING_BIT = ((size_t)1 << MANTISSA_BITS) >> 1;
|
||||||
|
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||||
|
|
||||||
|
value = value - 1;
|
||||||
|
|
||||||
|
size_t e = (bits::BITS - clz(value | LEADING_BIT)) - MANTISSA_BITS;
|
||||||
|
size_t shift_e = (e == 0) ? 0 : e - 1;
|
||||||
|
size_t m = (value >> shift_e) & MANTISSA_MASK;
|
||||||
|
|
||||||
|
return (e << MANTISSA_BITS) + m;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return bits::next_pow2_bits(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||||
|
constexpr static size_t to_exp_mant_const(size_t value)
|
||||||
|
{
|
||||||
|
value += ((size_t)1 << LOW_BITS) - 1;
|
||||||
|
value >>= LOW_BITS;
|
||||||
|
|
||||||
|
if (MANTISSA_BITS > 0)
|
||||||
|
{
|
||||||
|
size_t LEADING_BIT = (size_t)1 << (MANTISSA_BITS - 1);
|
||||||
|
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||||
|
|
||||||
|
value = value - 1;
|
||||||
|
|
||||||
|
size_t e =
|
||||||
|
(bits::BITS - clz_const(value | LEADING_BIT)) - MANTISSA_BITS;
|
||||||
|
size_t shift_e = (e == 0) ? 0 : e - 1;
|
||||||
|
size_t m = (value >> shift_e) & MANTISSA_MASK;
|
||||||
|
|
||||||
|
return (e << MANTISSA_BITS) + m;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return bits::next_pow2_bits_const(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<size_t MANTISSA_BITS, size_t LOW_BITS = 0>
|
||||||
|
constexpr static size_t from_exp_mant(size_t m_e)
|
||||||
|
{
|
||||||
|
if (MANTISSA_BITS > 0)
|
||||||
|
{
|
||||||
|
size_t MANTISSA_MASK = ((size_t)1 << MANTISSA_BITS) - 1;
|
||||||
|
size_t m = m_e & MANTISSA_MASK;
|
||||||
|
size_t e = m_e >> MANTISSA_BITS;
|
||||||
|
size_t b = e == 0 ? 0 : 1;
|
||||||
|
size_t shifted_e = e - b;
|
||||||
|
size_t extended_m = (m + ((size_t)b << MANTISSA_BITS)) + 1;
|
||||||
|
return extended_m << (shifted_e + LOW_BITS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (size_t)1 << (m_e + LOW_BITS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/ds/csv.h
Normal file
55
src/ds/csv.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class CSVStream
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::ostream* out;
|
||||||
|
bool first = true;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Endl
|
||||||
|
{};
|
||||||
|
|
||||||
|
Endl endl;
|
||||||
|
|
||||||
|
CSVStream(std::ostream* o) : out(o) {}
|
||||||
|
|
||||||
|
void preprint()
|
||||||
|
{
|
||||||
|
if (!first)
|
||||||
|
{
|
||||||
|
*out << ", ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSVStream& operator<<(const std::string& str)
|
||||||
|
{
|
||||||
|
preprint();
|
||||||
|
*out << str;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSVStream& operator<<(uint64_t u)
|
||||||
|
{
|
||||||
|
preprint();
|
||||||
|
*out << u;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSVStream& operator<<(Endl)
|
||||||
|
{
|
||||||
|
*out << std::endl;
|
||||||
|
first = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
120
src/ds/dllist.h
Normal file
120
src/ds/dllist.h
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class T, uintptr_t terminator = 0>
|
||||||
|
class DLList
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static_assert(
|
||||||
|
std::is_same<decltype(((T*)0)->prev), T*>::value, "T->prev must be a T*");
|
||||||
|
static_assert(
|
||||||
|
std::is_same<decltype(((T*)0)->next), T*>::value, "T->next must be a T*");
|
||||||
|
|
||||||
|
T* head = (T*)terminator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
T* get_head()
|
||||||
|
{
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* pop()
|
||||||
|
{
|
||||||
|
T* item = head;
|
||||||
|
|
||||||
|
if (item != (T*)terminator)
|
||||||
|
remove(item);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert(T* item)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check_not_contains(item);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
item->next = head;
|
||||||
|
item->prev = (T*)terminator;
|
||||||
|
|
||||||
|
if (head != (T*)terminator)
|
||||||
|
head->prev = item;
|
||||||
|
|
||||||
|
head = item;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(T* item)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check_contains(item);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (item->next != (T*)terminator)
|
||||||
|
item->next->prev = item->prev;
|
||||||
|
|
||||||
|
if (item->prev != (T*)terminator)
|
||||||
|
item->prev->next = item->next;
|
||||||
|
else
|
||||||
|
head = item->next;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_check_contains(T* item)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check();
|
||||||
|
T* curr = head;
|
||||||
|
|
||||||
|
while (curr != item)
|
||||||
|
{
|
||||||
|
assert(curr != (T*)terminator);
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
UNUSED(item);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_check_not_contains(T* item)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
debug_check();
|
||||||
|
T* curr = head;
|
||||||
|
|
||||||
|
while (curr != (T*)terminator)
|
||||||
|
{
|
||||||
|
assert(curr != item);
|
||||||
|
curr = curr->next;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
UNUSED(item);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_check()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
T* item = head;
|
||||||
|
T* prev = (T*)terminator;
|
||||||
|
|
||||||
|
while (item != (T*)terminator)
|
||||||
|
{
|
||||||
|
assert(item->prev == prev);
|
||||||
|
prev = item;
|
||||||
|
item = item->next;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
24
src/ds/flaglock.h
Normal file
24
src/ds/flaglock.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "bits.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class FlagLock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::atomic_flag& lock;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FlagLock(std::atomic_flag& lock) : lock(lock)
|
||||||
|
{
|
||||||
|
while (lock.test_and_set(std::memory_order_acquire))
|
||||||
|
bits::pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
~FlagLock()
|
||||||
|
{
|
||||||
|
lock.clear(std::memory_order_release);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
33
src/ds/helpers.h
Normal file
33
src/ds/helpers.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "flaglock.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* In some use cases we need to run before any of the C++ runtime has been
|
||||||
|
* initialised. This singleton class is design to not depend on the runtime.
|
||||||
|
*/
|
||||||
|
template<class Object, Object init() noexcept>
|
||||||
|
class Singleton
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
inline static Object& get()
|
||||||
|
{
|
||||||
|
static std::atomic_flag flag;
|
||||||
|
static std::atomic<bool> initialised;
|
||||||
|
static Object obj;
|
||||||
|
|
||||||
|
if (!initialised.load(std::memory_order_acquire))
|
||||||
|
{
|
||||||
|
FlagLock lock(flag);
|
||||||
|
if (!initialised)
|
||||||
|
{
|
||||||
|
obj = init();
|
||||||
|
initialised.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
75
src/ds/mpmcstack.h
Normal file
75
src/ds/mpmcstack.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "aba.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class T, Construction c = RequiresInit>
|
||||||
|
class MPMCStack
|
||||||
|
{
|
||||||
|
using ABA = ABA<T, c>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static_assert(
|
||||||
|
std::is_same<decltype(((T*)0)->next), std::atomic<T*>>::value,
|
||||||
|
"T->next must be a std::atomic<T*>");
|
||||||
|
|
||||||
|
ABA stack;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void push(T* item)
|
||||||
|
{
|
||||||
|
return push(item, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(T* first, T* last)
|
||||||
|
{
|
||||||
|
// Pushes an item on the stack.
|
||||||
|
auto cmp = stack.read();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
T* top = ABA::load(cmp);
|
||||||
|
last->next.store(top, std::memory_order_release);
|
||||||
|
} while (!stack.compare_exchange(cmp, first));
|
||||||
|
}
|
||||||
|
|
||||||
|
T* pop()
|
||||||
|
{
|
||||||
|
// Returns the next item. If the returned value is decommitted, it is
|
||||||
|
// possible for the read of top->next to segfault.
|
||||||
|
auto cmp = stack.read();
|
||||||
|
T* top;
|
||||||
|
T* next;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
top = ABA::load(cmp);
|
||||||
|
|
||||||
|
if (top == nullptr)
|
||||||
|
break;
|
||||||
|
|
||||||
|
next = top->next.load(std::memory_order_acquire);
|
||||||
|
} while (!stack.compare_exchange(cmp, next));
|
||||||
|
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* pop_all()
|
||||||
|
{
|
||||||
|
// Returns all items as a linked list, leaving an empty stack.
|
||||||
|
auto cmp = stack.read();
|
||||||
|
T* top;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
top = ABA::load(cmp);
|
||||||
|
|
||||||
|
if (top == nullptr)
|
||||||
|
break;
|
||||||
|
} while (!stack.compare_exchange(cmp, nullptr));
|
||||||
|
|
||||||
|
return top;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
99
src/ds/mpscq.h
Normal file
99
src/ds/mpscq.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "bits.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class T>
|
||||||
|
class MPSCQ
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static_assert(
|
||||||
|
std::is_same<decltype(((T*)0)->next), std::atomic<T*>>::value,
|
||||||
|
"T->next must be a std::atomic<T*>");
|
||||||
|
|
||||||
|
std::atomic<T*> head;
|
||||||
|
T* tail;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void invariant()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(head != nullptr);
|
||||||
|
assert(tail != nullptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(T* stub)
|
||||||
|
{
|
||||||
|
stub->next.store(nullptr, std::memory_order_relaxed);
|
||||||
|
tail = stub;
|
||||||
|
head.store(stub, std::memory_order_relaxed);
|
||||||
|
invariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
T* destroy()
|
||||||
|
{
|
||||||
|
T* tl = tail;
|
||||||
|
head.store(nullptr, std::memory_order_relaxed);
|
||||||
|
tail = nullptr;
|
||||||
|
return tl;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* get_head()
|
||||||
|
{
|
||||||
|
return head.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void push(T* item)
|
||||||
|
{
|
||||||
|
push(item, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_empty()
|
||||||
|
{
|
||||||
|
T* hd = head.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
return hd == tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void push(T* first, T* last)
|
||||||
|
{
|
||||||
|
// Pushes a list of messages to the queue. Each message from first to
|
||||||
|
// last should be linked together through their next pointers.
|
||||||
|
invariant();
|
||||||
|
last->next.store(nullptr, std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
T* prev = head.exchange(last, std::memory_order_relaxed);
|
||||||
|
prev->next.store(first, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<T*, T*> pop()
|
||||||
|
{
|
||||||
|
// Returns the next message and the tail message. If the next message
|
||||||
|
// is not null, the tail message should be freed by the caller.
|
||||||
|
invariant();
|
||||||
|
T* tl = tail;
|
||||||
|
T* next = tl->next.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (next != nullptr)
|
||||||
|
{
|
||||||
|
tail = next;
|
||||||
|
|
||||||
|
assert(tail);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
invariant();
|
||||||
|
return std::make_pair(next, tl);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* peek()
|
||||||
|
{
|
||||||
|
return tail->next.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
1091
src/mem/alloc.h
Normal file
1091
src/mem/alloc.h
Normal file
File diff suppressed because it is too large
Load Diff
121
src/mem/allocconfig.h
Normal file
121
src/mem/allocconfig.h
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
enum ZeroMem
|
||||||
|
{
|
||||||
|
NoZero,
|
||||||
|
YesZero
|
||||||
|
};
|
||||||
|
|
||||||
|
// 0 intermediate bits results in power of 2 small allocs. 1 intermediate
|
||||||
|
// bit gives additional sizeclasses at the midpoint between each power of 2.
|
||||||
|
// 2 intermediate bits gives 3 intermediate sizeclasses, etc.
|
||||||
|
static constexpr size_t INTERMEDIATE_BITS =
|
||||||
|
#ifdef USE_INTERMEDIATE_BITS
|
||||||
|
USE_INTERMEDIATE_BITS
|
||||||
|
#else
|
||||||
|
2
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
// Return remote small allocs when the local cache reaches this size.
|
||||||
|
static constexpr size_t REMOTE_CACHE =
|
||||||
|
#ifdef USE_REMOTE_CACHE
|
||||||
|
USE_REMOTE_CACHE
|
||||||
|
#else
|
||||||
|
1 << 20
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
// Handle at most this many object from the remote dealloc queue at a time.
|
||||||
|
static constexpr size_t REMOTE_BATCH =
|
||||||
|
#ifdef USE_REMOTE_BATCH
|
||||||
|
REMOTE_BATCH
|
||||||
|
#else
|
||||||
|
64
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
static constexpr size_t RESERVE_MULTIPLE =
|
||||||
|
#ifdef USE_RESERVE_MULTIPLE
|
||||||
|
USE_RESERVE_MULTIPLE
|
||||||
|
#else
|
||||||
|
bits::is64() ? 16 : 2
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
enum DecommitStrategy
|
||||||
|
{
|
||||||
|
DecommitNone,
|
||||||
|
DecommitSuper,
|
||||||
|
DecommitAll
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr DecommitStrategy decommit_strategy =
|
||||||
|
#ifdef USE_DECOMMIT_STRATEGY
|
||||||
|
USE_DECOMMIT_STRATEGY
|
||||||
|
#else
|
||||||
|
DecommitSuper
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
|
||||||
|
// The remaining values are derived, not configurable.
|
||||||
|
|
||||||
|
// Used to isolate values on cache lines to prevent false sharing.
|
||||||
|
static constexpr size_t CACHELINE_SIZE = 64;
|
||||||
|
|
||||||
|
// Used to keep Superslab metadata committed.
|
||||||
|
static constexpr size_t OS_PAGE_SIZE = 0x1000;
|
||||||
|
static constexpr size_t PAGE_ALIGNED_SIZE = OS_PAGE_SIZE << INTERMEDIATE_BITS;
|
||||||
|
// Some system headers (e.g. Linux' sys/user.h, FreeBSD's machine/param.h)
|
||||||
|
// define `PAGE_SIZE` as a macro. We don't use `PAGE_SIZE` as our variable
|
||||||
|
// name, to avoid conflicts, but if we do see a macro definition then check
|
||||||
|
// that our value matches the platform's expected value.
|
||||||
|
#ifdef PAGE_SIZE
|
||||||
|
static_assert(
|
||||||
|
PAGE_SIZE == OS_PAGE_SIZE,
|
||||||
|
"Page size from system header does not match snmalloc config page size.");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Minimum allocation size is space for two pointers.
|
||||||
|
static constexpr size_t MIN_ALLOC_BITS = bits::is64() ? 4 : 3;
|
||||||
|
static constexpr size_t MIN_ALLOC_SIZE = 1 << MIN_ALLOC_BITS;
|
||||||
|
|
||||||
|
// Slabs are 64 kb.
|
||||||
|
static constexpr size_t SLAB_BITS = 16;
|
||||||
|
static constexpr size_t SLAB_SIZE = 1 << SLAB_BITS;
|
||||||
|
static constexpr size_t SLAB_MASK = ~(SLAB_SIZE - 1);
|
||||||
|
|
||||||
|
// Superslabs are composed of this many slabs. Slab offsets are encoded as
|
||||||
|
// a byte, so the maximum count is 256. This must be a power of two to
|
||||||
|
// allow fast masking to find a superslab start address.
|
||||||
|
static constexpr size_t SLAB_COUNT_BITS = 8;
|
||||||
|
static constexpr size_t SLAB_COUNT = 1 << SLAB_COUNT_BITS;
|
||||||
|
static constexpr size_t SUPERSLAB_SIZE = SLAB_SIZE * SLAB_COUNT;
|
||||||
|
static constexpr size_t SUPERSLAB_MASK = ~(SUPERSLAB_SIZE - 1);
|
||||||
|
static constexpr size_t SUPERSLAB_BITS = SLAB_BITS + SLAB_COUNT_BITS;
|
||||||
|
static constexpr size_t RESERVE_SIZE = SUPERSLAB_SIZE * RESERVE_MULTIPLE;
|
||||||
|
|
||||||
|
// Number of slots for remote deallocation.
|
||||||
|
static constexpr size_t REMOTE_SLOT_BITS = 6;
|
||||||
|
static constexpr size_t REMOTE_SLOTS = 1 << REMOTE_SLOT_BITS;
|
||||||
|
static constexpr size_t REMOTE_MASK = REMOTE_SLOTS - 1;
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
INTERMEDIATE_BITS < MIN_ALLOC_BITS,
|
||||||
|
"INTERMEDIATE_BITS must be less than MIN_ALLOC_BITS");
|
||||||
|
static_assert(
|
||||||
|
MIN_ALLOC_SIZE >= (sizeof(void*) * 2),
|
||||||
|
"MIN_ALLOC_SIZE must be sufficient for two pointers");
|
||||||
|
static_assert(
|
||||||
|
SLAB_BITS == (sizeof(uint16_t) * 8),
|
||||||
|
"SLAB_BITS must be the bits in a uint16_t");
|
||||||
|
static_assert(
|
||||||
|
SLAB_COUNT == bits::next_pow2_const(SLAB_COUNT),
|
||||||
|
"SLAB_COUNT must be a power of 2");
|
||||||
|
static_assert(
|
||||||
|
SLAB_COUNT <= (UINT8_MAX + 1), "SLAB_COUNT must fit in a uint8_t");
|
||||||
|
};
|
||||||
19
src/mem/allocslab.h
Normal file
19
src/mem/allocslab.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../mem/baseslab.h"
|
||||||
|
#include "remoteallocator.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Allocslab : public Baseslab
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
RemoteAllocator* allocator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RemoteAllocator* get_allocator()
|
||||||
|
{
|
||||||
|
return allocator;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
391
src/mem/allocstats.h
Normal file
391
src/mem/allocstats.h
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
# include "../ds/csv.h"
|
||||||
|
# include "sizeclass.h"
|
||||||
|
|
||||||
|
# include <cstring>
|
||||||
|
# include <iostream>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<size_t N, size_t LARGE_N>
|
||||||
|
struct AllocStats
|
||||||
|
{
|
||||||
|
struct CurrentMaxPair
|
||||||
|
{
|
||||||
|
size_t current = 0;
|
||||||
|
size_t max = 0;
|
||||||
|
|
||||||
|
void inc()
|
||||||
|
{
|
||||||
|
current++;
|
||||||
|
if (current > max)
|
||||||
|
max++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dec()
|
||||||
|
{
|
||||||
|
current--;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_empty()
|
||||||
|
{
|
||||||
|
return current == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_unused()
|
||||||
|
{
|
||||||
|
return max == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(CurrentMaxPair& that)
|
||||||
|
{
|
||||||
|
current += that.current;
|
||||||
|
max += that.max;
|
||||||
|
}
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
void print(CSVStream& csv, size_t multiplier = 1)
|
||||||
|
{
|
||||||
|
csv << current * multiplier << max * multiplier;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stats
|
||||||
|
{
|
||||||
|
CurrentMaxPair count;
|
||||||
|
CurrentMaxPair slab_count;
|
||||||
|
uint64_t time = bits::tick();
|
||||||
|
uint64_t ticks = 0;
|
||||||
|
double online_average = 0;
|
||||||
|
|
||||||
|
bool is_empty()
|
||||||
|
{
|
||||||
|
return count.is_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(Stats& that)
|
||||||
|
{
|
||||||
|
count.add(that.count);
|
||||||
|
slab_count.add(that.slab_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addToRunningAverage()
|
||||||
|
{
|
||||||
|
uint64_t now = bits::tick();
|
||||||
|
|
||||||
|
if (slab_count.current != 0)
|
||||||
|
{
|
||||||
|
double occupancy = (double)count.current / (double)slab_count.current;
|
||||||
|
uint64_t duration = now - time;
|
||||||
|
|
||||||
|
if (ticks == 0)
|
||||||
|
online_average = occupancy;
|
||||||
|
else
|
||||||
|
online_average += ((occupancy - online_average) * duration) / ticks;
|
||||||
|
|
||||||
|
ticks += duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
time = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
void
|
||||||
|
print(CSVStream& csv, size_t multiplier = 1, size_t slab_multiplier = 1)
|
||||||
|
{
|
||||||
|
// Keep in sync with header lower down
|
||||||
|
count.print(csv, multiplier);
|
||||||
|
slab_count.print(csv, slab_multiplier);
|
||||||
|
size_t average = (size_t)(online_average * multiplier);
|
||||||
|
|
||||||
|
csv << average << (slab_multiplier - average) * slab_count.max
|
||||||
|
<< csv.endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
static constexpr size_t BUCKETS_BITS = 4;
|
||||||
|
static constexpr size_t BUCKETS = 1 << BUCKETS_BITS;
|
||||||
|
static constexpr size_t TOTAL_BUCKETS =
|
||||||
|
bits::to_exp_mant_const<BUCKETS_BITS>(
|
||||||
|
((size_t)1 << (bits::ADDRESS_BITS - 1)));
|
||||||
|
|
||||||
|
Stats sizeclass[N];
|
||||||
|
Stats large[LARGE_N];
|
||||||
|
|
||||||
|
size_t remote_freed = 0;
|
||||||
|
size_t remote_posted = 0;
|
||||||
|
size_t remote_received = 0;
|
||||||
|
size_t superslab_push_count = 0;
|
||||||
|
size_t superslab_pop_count = 0;
|
||||||
|
size_t superslab_fresh_count = 0;
|
||||||
|
size_t segment_count = 0;
|
||||||
|
size_t bucketed_requests[TOTAL_BUCKETS] = {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void alloc_request(size_t size)
|
||||||
|
{
|
||||||
|
UNUSED(size);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
bucketed_requests[bits::to_exp_mant<BUCKETS_BITS>(size)]++;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_empty()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
for (size_t i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
if (!sizeclass[i].is_empty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < LARGE_N; i++)
|
||||||
|
{
|
||||||
|
if (!large[i].is_empty())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (remote_freed == remote_posted);
|
||||||
|
#else
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sizeclass_alloc(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
sizeclass[sc].addToRunningAverage();
|
||||||
|
sizeclass[sc].count.inc();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sizeclass_dealloc(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
sizeclass[sc].addToRunningAverage();
|
||||||
|
sizeclass[sc].count.dec();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void large_alloc(size_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
large[sc].count.inc();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sizeclass_alloc_slab(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
sizeclass[sc].addToRunningAverage();
|
||||||
|
sizeclass[sc].slab_count.inc();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void sizeclass_dealloc_slab(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
sizeclass[sc].addToRunningAverage();
|
||||||
|
sizeclass[sc].slab_count.dec();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void large_dealloc(size_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
large[sc].count.dec();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void segment_create()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
segment_count++;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void superslab_pop()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
superslab_pop_count++;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void superslab_push()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
superslab_push_count++;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void superslab_fresh()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
superslab_fresh_count++;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote_free(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
remote_freed += sizeclass_to_size(sc);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote_post()
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
remote_posted = remote_freed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void remote_receive(uint8_t sc)
|
||||||
|
{
|
||||||
|
UNUSED(sc);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
remote_received += sizeclass_to_size(sc);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(AllocStats<N, LARGE_N>& that)
|
||||||
|
{
|
||||||
|
UNUSED(that);
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
for (size_t i = 0; i < N; i++)
|
||||||
|
sizeclass[i].add(that.sizeclass[i]);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < LARGE_N; i++)
|
||||||
|
large[i].add(that.large[i]);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < TOTAL_BUCKETS; i++)
|
||||||
|
bucketed_requests[i] += that.bucketed_requests[i];
|
||||||
|
|
||||||
|
remote_freed += that.remote_freed;
|
||||||
|
remote_posted += that.remote_posted;
|
||||||
|
remote_received += that.remote_received;
|
||||||
|
superslab_pop_count += that.superslab_pop_count;
|
||||||
|
superslab_push_count += that.superslab_push_count;
|
||||||
|
superslab_fresh_count += that.superslab_fresh_count;
|
||||||
|
segment_count += that.segment_count;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
template<class Alloc>
|
||||||
|
void print(std::ostream& o, uint64_t dumpid = 0, uint64_t allocatorid = 0)
|
||||||
|
{
|
||||||
|
UNUSED(o);
|
||||||
|
UNUSED(dumpid);
|
||||||
|
UNUSED(allocatorid);
|
||||||
|
|
||||||
|
CSVStream csv(&o);
|
||||||
|
|
||||||
|
if (dumpid == 0)
|
||||||
|
{
|
||||||
|
// Output headers for initial dump
|
||||||
|
// Keep in sync with data dump
|
||||||
|
csv << "GlobalStats"
|
||||||
|
<< "DumpID"
|
||||||
|
<< "AllocatorID"
|
||||||
|
<< "Remote freed"
|
||||||
|
<< "Remote posted"
|
||||||
|
<< "Remote received"
|
||||||
|
<< "Superslab pop"
|
||||||
|
<< "Superslab push"
|
||||||
|
<< "Superslab fresh"
|
||||||
|
<< "Segments" << csv.endl;
|
||||||
|
|
||||||
|
csv << "BucketedStats"
|
||||||
|
<< "DumpID"
|
||||||
|
<< "AllocatorID"
|
||||||
|
<< "Size group"
|
||||||
|
<< "Size"
|
||||||
|
<< "Current bytes"
|
||||||
|
<< "Max bytes"
|
||||||
|
<< "Current Slab bytes"
|
||||||
|
<< "Max Slab bytes"
|
||||||
|
<< "Average Slab Usage"
|
||||||
|
<< "Average wasted space" << csv.endl;
|
||||||
|
|
||||||
|
csv << "AllocSizes"
|
||||||
|
<< "DumpID"
|
||||||
|
<< "AllocatorID"
|
||||||
|
<< "ClassID"
|
||||||
|
<< "Low size"
|
||||||
|
<< "High size"
|
||||||
|
<< "Count" << csv.endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < N; i++)
|
||||||
|
{
|
||||||
|
if (sizeclass[i].count.is_unused())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sizeclass[i].addToRunningAverage();
|
||||||
|
|
||||||
|
csv << "BucketedStats" << dumpid << allocatorid << i
|
||||||
|
<< sizeclass_to_size(i);
|
||||||
|
|
||||||
|
sizeclass[i].print(csv, sizeclass_to_size(i), SLAB_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < LARGE_N; i++)
|
||||||
|
{
|
||||||
|
if (large[i].count.is_unused())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
csv << "BucketedStats" << dumpid << allocatorid << (i + N)
|
||||||
|
<< large_sizeclass_to_size(i);
|
||||||
|
|
||||||
|
large[i].print(csv, large_sizeclass_to_size(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t low = 0;
|
||||||
|
size_t high = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < TOTAL_BUCKETS; i++)
|
||||||
|
{
|
||||||
|
low = high + 1;
|
||||||
|
high = bits::from_exp_mant<BUCKETS_BITS>(i);
|
||||||
|
|
||||||
|
if (bucketed_requests[i] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
csv << "AllocSizes" << dumpid << allocatorid << i << low << high
|
||||||
|
<< bucketed_requests[i] << csv.endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
csv << "GlobalStats" << dumpid << allocatorid << remote_freed
|
||||||
|
<< remote_posted << remote_received << superslab_pop_count
|
||||||
|
<< superslab_push_count << superslab_fresh_count << segment_count
|
||||||
|
<< csv.endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
}
|
||||||
27
src/mem/baseslab.h
Normal file
27
src/mem/baseslab.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/mpmcstack.h"
|
||||||
|
#include "allocconfig.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
enum SlabKind
|
||||||
|
{
|
||||||
|
Fresh = 0,
|
||||||
|
Large,
|
||||||
|
Medium,
|
||||||
|
Super
|
||||||
|
};
|
||||||
|
|
||||||
|
class Baseslab
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
SlabKind kind;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SlabKind get_kind()
|
||||||
|
{
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
171
src/mem/globalalloc.h
Normal file
171
src/mem/globalalloc.h
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/helpers.h"
|
||||||
|
#include "alloc.h"
|
||||||
|
#include "typealloc.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class MemoryProvider>
|
||||||
|
class AllocPool : TypeAlloc<Allocator<MemoryProvider>, MemoryProvider>
|
||||||
|
{
|
||||||
|
using Alloc = Allocator<MemoryProvider>;
|
||||||
|
using Parent = TypeAlloc<Allocator<MemoryProvider>, MemoryProvider>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static AllocPool* make(MemoryProvider& mp)
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
sizeof(AllocPool) == sizeof(Parent),
|
||||||
|
"You cannot add fields to this class.");
|
||||||
|
// This cast is safe due to the static assert.
|
||||||
|
return (AllocPool*)Parent::make(mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AllocPool* make() noexcept
|
||||||
|
{
|
||||||
|
return make(default_memory_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
Alloc* acquire()
|
||||||
|
{
|
||||||
|
return Parent::alloc(Parent::memory_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void release(Alloc* a)
|
||||||
|
{
|
||||||
|
Parent::dealloc(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void aggregate_stats(Stats& stats)
|
||||||
|
{
|
||||||
|
auto* alloc = Parent::iterate();
|
||||||
|
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
stats.add(alloc->stats());
|
||||||
|
alloc = Parent::iterate(alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_all_stats(std::ostream& o, uint64_t dumpid = 0)
|
||||||
|
{
|
||||||
|
auto alloc = Parent::iterate();
|
||||||
|
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
alloc->stats().template print<Alloc>(o, dumpid, alloc->id());
|
||||||
|
alloc = Parent::iterate(alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_unused()
|
||||||
|
{
|
||||||
|
#ifndef USE_MALLOC
|
||||||
|
// Call this periodically to free and coalesce memory allocated by
|
||||||
|
// allocators that are not currently in use by any thread.
|
||||||
|
// One atomic operation to extract the stack, another to restore it.
|
||||||
|
// Handling the message queue for each stack is non-atomic.
|
||||||
|
auto* first = Parent::extract();
|
||||||
|
auto* alloc = first;
|
||||||
|
decltype(alloc) last;
|
||||||
|
|
||||||
|
if (alloc != nullptr)
|
||||||
|
{
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
alloc->handle_message_queue();
|
||||||
|
last = alloc;
|
||||||
|
alloc = Parent::extract(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(first, last);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_check_empty()
|
||||||
|
{
|
||||||
|
#ifndef USE_MALLOC
|
||||||
|
// This is a debugging function. It checks that all memory from all
|
||||||
|
// allocators has been freed.
|
||||||
|
size_t alloc_count = 0;
|
||||||
|
|
||||||
|
auto* alloc = Parent::iterate();
|
||||||
|
|
||||||
|
// Count the linked allocators.
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
alloc = Parent::iterate(alloc);
|
||||||
|
alloc_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
while (!done)
|
||||||
|
{
|
||||||
|
done = true;
|
||||||
|
alloc = Parent::iterate();
|
||||||
|
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
// Destroy the message queue so that it has no stub message.
|
||||||
|
Remote* p = alloc->message_queue().destroy();
|
||||||
|
|
||||||
|
while (p != nullptr)
|
||||||
|
{
|
||||||
|
Remote* next = p->non_atomic_next;
|
||||||
|
alloc->handle_dealloc_remote(p);
|
||||||
|
p = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place the static stub message on the queue.
|
||||||
|
alloc->init_message_queue();
|
||||||
|
|
||||||
|
// Post all remotes, including forwarded ones. If any allocator posts,
|
||||||
|
// repeat the loop.
|
||||||
|
if (alloc->remote.size > 0)
|
||||||
|
{
|
||||||
|
alloc->stats().remote_post();
|
||||||
|
alloc->remote.post(alloc->id());
|
||||||
|
done = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc = Parent::iterate(alloc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc = Parent::iterate();
|
||||||
|
size_t empty_count = 0;
|
||||||
|
|
||||||
|
while (alloc != nullptr)
|
||||||
|
{
|
||||||
|
// Check that the allocator has freed all memory.
|
||||||
|
if (alloc->stats().is_empty())
|
||||||
|
empty_count++;
|
||||||
|
|
||||||
|
alloc = Parent::iterate(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alloc_count != empty_count)
|
||||||
|
error("Incorrect number of allocators");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline AllocPool<GlobalVirtual>*& current_alloc_pool()
|
||||||
|
{
|
||||||
|
return Singleton<
|
||||||
|
AllocPool<GlobalVirtual>*,
|
||||||
|
AllocPool<GlobalVirtual>::make>::get();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class MemoryProvider>
|
||||||
|
inline AllocPool<MemoryProvider>* make_alloc_pool(MemoryProvider& mp)
|
||||||
|
{
|
||||||
|
return AllocPool<MemoryProvider>::make(mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
using Alloc = Allocator<GlobalVirtual>;
|
||||||
|
}
|
||||||
207
src/mem/largealloc.h
Normal file
207
src/mem/largealloc.h
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/flaglock.h"
|
||||||
|
#include "../ds/mpmcstack.h"
|
||||||
|
#include "../pal/pal.h"
|
||||||
|
#include "allocstats.h"
|
||||||
|
#include "baseslab.h"
|
||||||
|
#include "sizeclass.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Largeslab : public Baseslab
|
||||||
|
{
|
||||||
|
// This is the view of a contiguous memory area when it is being kept
|
||||||
|
// in the global size-classed caches of available contiguous memory areas.
|
||||||
|
private:
|
||||||
|
template<class a, Construction c>
|
||||||
|
friend class MPMCStack;
|
||||||
|
std::atomic<Largeslab*> next;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void init()
|
||||||
|
{
|
||||||
|
kind = Large;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This represents the state that the large allcoator needs to add to the
|
||||||
|
// global state of the allocator. This is currently stored in the memory
|
||||||
|
// provider, so we add this in.
|
||||||
|
template<class MemoryProviderState>
|
||||||
|
class MemoryProviderStateMixin : public MemoryProviderState
|
||||||
|
{
|
||||||
|
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||||
|
size_t bump;
|
||||||
|
size_t remaining;
|
||||||
|
|
||||||
|
std::pair<void*, size_t> reserve_block() noexcept
|
||||||
|
{
|
||||||
|
size_t size = SUPERSLAB_SIZE;
|
||||||
|
void* r = ((MemoryProviderState*)this)
|
||||||
|
->template reserve<false>(&size, SUPERSLAB_SIZE);
|
||||||
|
|
||||||
|
if (size < SUPERSLAB_SIZE)
|
||||||
|
error("out of memory");
|
||||||
|
|
||||||
|
((MemoryProviderState*)this)
|
||||||
|
->template notify_using<NoZero>(r, OS_PAGE_SIZE);
|
||||||
|
return std::make_pair(r, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Stack of large allocations that have been returned for reuse.
|
||||||
|
*/
|
||||||
|
MPMCStack<Largeslab, PreZeroed> large_stack[NUM_LARGE_CLASSES];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive allocator for structure that are required before
|
||||||
|
* the allocator can be running.
|
||||||
|
***/
|
||||||
|
void* alloc_chunk(size_t size)
|
||||||
|
{
|
||||||
|
// Cache line align
|
||||||
|
size = bits::align_up(size, 64);
|
||||||
|
|
||||||
|
void* p;
|
||||||
|
{
|
||||||
|
FlagLock f(lock);
|
||||||
|
|
||||||
|
if (remaining < size)
|
||||||
|
{
|
||||||
|
auto r_size = reserve_block();
|
||||||
|
|
||||||
|
bump = (size_t)r_size.first;
|
||||||
|
remaining = r_size.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = (void*)bump;
|
||||||
|
bump += size;
|
||||||
|
remaining -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto page_start = bits::align_down((size_t)p, OS_PAGE_SIZE);
|
||||||
|
auto page_end = bits::align_up((size_t)p + size, OS_PAGE_SIZE);
|
||||||
|
|
||||||
|
((MemoryProviderState*)this)
|
||||||
|
->template notify_using<NoZero>(
|
||||||
|
(void*)page_start, page_end - page_start);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using Stats = AllocStats<NUM_SIZECLASSES, NUM_LARGE_CLASSES>;
|
||||||
|
|
||||||
|
enum AllowReserve
|
||||||
|
{
|
||||||
|
NoReserve,
|
||||||
|
YesReserve
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class MemoryProvider>
|
||||||
|
class LargeAlloc
|
||||||
|
{
|
||||||
|
void* reserved_start = nullptr;
|
||||||
|
void* reserved_end = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// This will be a zero-size structure if stats are not enabled.
|
||||||
|
Stats stats;
|
||||||
|
|
||||||
|
MemoryProvider& memory_provider;
|
||||||
|
|
||||||
|
LargeAlloc(MemoryProvider& mp) : memory_provider(mp) {}
|
||||||
|
|
||||||
|
template<AllowReserve allow_reserve>
|
||||||
|
bool reserve_memory(size_t need, size_t add)
|
||||||
|
{
|
||||||
|
if (((size_t)reserved_start + need) > (size_t)reserved_end)
|
||||||
|
{
|
||||||
|
if (allow_reserve == YesReserve)
|
||||||
|
{
|
||||||
|
stats.segment_create();
|
||||||
|
reserved_start =
|
||||||
|
memory_provider.template reserve<false>(&add, SUPERSLAB_SIZE);
|
||||||
|
reserved_end = (void*)((size_t)reserved_start + add);
|
||||||
|
reserved_start =
|
||||||
|
(void*)bits::align_up((size_t)reserved_start, SUPERSLAB_SIZE);
|
||||||
|
|
||||||
|
if (add < need)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ZeroMem zero_mem = NoZero, AllowReserve allow_reserve = YesReserve>
|
||||||
|
void* alloc(size_t large_class, size_t size)
|
||||||
|
{
|
||||||
|
size_t rsize = ((size_t)1 << SUPERSLAB_BITS) << large_class;
|
||||||
|
if (size == 0)
|
||||||
|
size = rsize;
|
||||||
|
|
||||||
|
void* p = memory_provider.large_stack[large_class].pop();
|
||||||
|
|
||||||
|
if (p == nullptr)
|
||||||
|
{
|
||||||
|
assert(reserved_start <= reserved_end);
|
||||||
|
size_t add;
|
||||||
|
|
||||||
|
if ((rsize + SUPERSLAB_SIZE) < RESERVE_SIZE)
|
||||||
|
add = RESERVE_SIZE;
|
||||||
|
else
|
||||||
|
add = rsize + SUPERSLAB_SIZE;
|
||||||
|
|
||||||
|
if (!reserve_memory<allow_reserve>(rsize, add))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
p = (void*)reserved_start;
|
||||||
|
reserved_start = (void*)((size_t)p + rsize);
|
||||||
|
|
||||||
|
// All memory is zeroed since it comes from reserved space.
|
||||||
|
memory_provider.template notify_using<NoZero>(p, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ((decommit_strategy != DecommitNone) || (large_class > 0))
|
||||||
|
{
|
||||||
|
// Only the first page needs to be zeroed, as this was decommitted.
|
||||||
|
if (zero_mem == YesZero)
|
||||||
|
memory_provider.template zero<true>(p, OS_PAGE_SIZE);
|
||||||
|
|
||||||
|
memory_provider.template notify_using<zero_mem>(
|
||||||
|
(void*)((size_t)p + OS_PAGE_SIZE), size - OS_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This is a superslab that has not been decommitted.
|
||||||
|
if (zero_mem == YesZero)
|
||||||
|
memory_provider.template zero<true>(p, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dealloc(void* p, size_t large_class)
|
||||||
|
{
|
||||||
|
memory_provider.large_stack[large_class].push((Largeslab*)p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using GlobalVirtual = MemoryProviderStateMixin<Pal>;
|
||||||
|
/**
|
||||||
|
* The memory provider that will be used if no other provider is explicitly
|
||||||
|
* passed as an argument.
|
||||||
|
*/
|
||||||
|
HEADER_GLOBAL GlobalVirtual default_memory_provider;
|
||||||
|
}
|
||||||
130
src/mem/mediumslab.h
Normal file
130
src/mem/mediumslab.h
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/dllist.h"
|
||||||
|
#include "allocconfig.h"
|
||||||
|
#include "allocslab.h"
|
||||||
|
#include "sizeclass.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Mediumslab : public Allocslab
|
||||||
|
{
|
||||||
|
// This is the view of a 16 mb area when it is being used to allocate
|
||||||
|
// medium sized classes: 64 kb to 16 mb, non-inclusive.
|
||||||
|
private:
|
||||||
|
friend DLList<Mediumslab>;
|
||||||
|
|
||||||
|
// Keep the allocator pointer on a separate cache line. It is read by
|
||||||
|
// other threads, and does not change, so we avoid false sharing.
|
||||||
|
alignas(CACHELINE_SIZE) Mediumslab* next;
|
||||||
|
Mediumslab* prev;
|
||||||
|
|
||||||
|
uint16_t free;
|
||||||
|
uint8_t head;
|
||||||
|
uint8_t sizeclass;
|
||||||
|
uint16_t stack[SLAB_COUNT - 1];
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr uint32_t header_size()
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
sizeof(Mediumslab) < OS_PAGE_SIZE,
|
||||||
|
"Mediumslab header size must be less than the page size");
|
||||||
|
static_assert(
|
||||||
|
sizeof(Mediumslab) < SLAB_SIZE,
|
||||||
|
"Mediumslab header size must be less than the slab size");
|
||||||
|
|
||||||
|
// Always use a full page as the header, in order to get page sized
|
||||||
|
// alignment of individual allocations.
|
||||||
|
return OS_PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Mediumslab* get(void* p)
|
||||||
|
{
|
||||||
|
return (Mediumslab*)((size_t)p & SUPERSLAB_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(RemoteAllocator* alloc, uint8_t sc, size_t rsize)
|
||||||
|
{
|
||||||
|
assert(sc >= NUM_SMALL_CLASSES);
|
||||||
|
assert((sc - NUM_SMALL_CLASSES) < NUM_MEDIUM_CLASSES);
|
||||||
|
|
||||||
|
allocator = alloc;
|
||||||
|
head = 0;
|
||||||
|
|
||||||
|
// If this was previously a Mediumslab of the same sizeclass, don't
|
||||||
|
// initialise the allocation stack.
|
||||||
|
if ((kind != Medium) || (sizeclass != sc))
|
||||||
|
{
|
||||||
|
sizeclass = sc;
|
||||||
|
uint16_t ssize = (uint16_t)(rsize >> 8);
|
||||||
|
kind = Medium;
|
||||||
|
free = medium_slab_free(sc);
|
||||||
|
for (uint16_t i = free; i > 0; i--)
|
||||||
|
stack[free - i] = (uint16_t)((SUPERSLAB_SIZE >> 8) - (i * ssize));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
assert(free == medium_slab_free(sc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t get_sizeclass()
|
||||||
|
{
|
||||||
|
return sizeclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ZeroMem zero_mem, typename MemoryProvider>
|
||||||
|
void* alloc(size_t size, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
assert(!full());
|
||||||
|
|
||||||
|
uint16_t index = stack[head++];
|
||||||
|
void* p = (void*)((size_t)this + ((size_t)index << 8));
|
||||||
|
free--;
|
||||||
|
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, OS_PAGE_SIZE));
|
||||||
|
size = bits::align_up(size, OS_PAGE_SIZE);
|
||||||
|
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
memory_provider.template notify_using<zero_mem>(p, size);
|
||||||
|
else if (zero_mem == YesZero)
|
||||||
|
memory_provider.template zero<true>(p, size);
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
bool dealloc(void* p, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
assert(head > 0);
|
||||||
|
|
||||||
|
// Returns true if the Mediumslab was full before this deallocation.
|
||||||
|
bool was_full = full();
|
||||||
|
free++;
|
||||||
|
stack[--head] = pointer_to_index(p);
|
||||||
|
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
memory_provider.notify_not_using(p, sizeclass_to_size(sizeclass));
|
||||||
|
|
||||||
|
return was_full;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool full()
|
||||||
|
{
|
||||||
|
return free == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty()
|
||||||
|
{
|
||||||
|
return head == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t pointer_to_index(void* p)
|
||||||
|
{
|
||||||
|
// Get the offset from the slab for a memory location.
|
||||||
|
return (uint16_t)(((size_t)p - (size_t)this) >> 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
166
src/mem/metaslab.h
Normal file
166
src/mem/metaslab.h
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/dllist.h"
|
||||||
|
#include "sizeclass.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Slab;
|
||||||
|
|
||||||
|
struct SlabLink
|
||||||
|
{
|
||||||
|
SlabLink* prev;
|
||||||
|
SlabLink* next;
|
||||||
|
|
||||||
|
Slab* get_slab()
|
||||||
|
{
|
||||||
|
return (Slab*)((size_t)this & SLAB_MASK);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using SlabList = DLList<SlabLink, ~(uintptr_t)0>;
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
sizeof(SlabLink) <= MIN_ALLOC_SIZE,
|
||||||
|
"Need to be able to pack a SlabLink into any free small alloc");
|
||||||
|
|
||||||
|
static constexpr uint16_t SLABLINK_INDEX =
|
||||||
|
(uint16_t)(SLAB_SIZE - sizeof(SlabLink));
|
||||||
|
|
||||||
|
// The Metaslab represent the status of a single slab.
|
||||||
|
// This can be either a short or a standard slab.
|
||||||
|
class Metaslab
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// How many entries are used in this slab.
|
||||||
|
uint16_t used;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Bump free list of unused entries in this sizeclass.
|
||||||
|
// If the bottom bit is 1, then this represents a bump_ptr
|
||||||
|
// of where we have allocated up to in this slab. Otherwise,
|
||||||
|
// it represents the location of the first block in the free
|
||||||
|
// list. The free list is chained through deallocated blocks.
|
||||||
|
// It either terminates with a bump ptr, or if all the space is in
|
||||||
|
// the free list, then the last block will be also referenced by
|
||||||
|
// link.
|
||||||
|
// Note that, in the case that this is the first block in the size
|
||||||
|
// class list, where all the unused memory is in the free list,
|
||||||
|
// then the last block can both be interpreted as a final bump
|
||||||
|
// pointer entry, and the first entry in the doubly linked list.
|
||||||
|
// The terminal value in the free list, and the terminal value in
|
||||||
|
// the SlabLink previous field will alias. The SlabLink uses ~0 for
|
||||||
|
// its terminal value to be a valid terminal bump ptr.
|
||||||
|
uint16_t head;
|
||||||
|
// When a slab has free space it will be on the has space list for
|
||||||
|
// that size class. We use an empty block in this slab to be the
|
||||||
|
// doubly linked node into that size class's free list.
|
||||||
|
uint16_t link;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
uint8_t sizeclass;
|
||||||
|
uint8_t next;
|
||||||
|
};
|
||||||
|
|
||||||
|
void add_use()
|
||||||
|
{
|
||||||
|
used++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sub_use()
|
||||||
|
{
|
||||||
|
used--;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_unused()
|
||||||
|
{
|
||||||
|
used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_unused()
|
||||||
|
{
|
||||||
|
return used == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_full()
|
||||||
|
{
|
||||||
|
return (head & 2) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_full()
|
||||||
|
{
|
||||||
|
assert(head == 1);
|
||||||
|
head = (uint16_t)~0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SlabLink* get_link(Slab* slab)
|
||||||
|
{
|
||||||
|
return (SlabLink*)((size_t)slab + link);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid_head(bool is_short)
|
||||||
|
{
|
||||||
|
size_t size = sizeclass_to_size(sizeclass);
|
||||||
|
size_t offset = get_slab_offset(sizeclass, is_short);
|
||||||
|
return ((((head & ~(size_t)1) - (offset & ~(size_t)1)) % size) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void debug_slab_invariant(bool is_short, Slab* slab)
|
||||||
|
{
|
||||||
|
#if !defined(NDEBUG) && !defined(SNMALLOC_CHEAP_CHECKS)
|
||||||
|
size_t size = sizeclass_to_size(sizeclass);
|
||||||
|
size_t offset = get_slab_offset(sizeclass, is_short) - 1;
|
||||||
|
|
||||||
|
size_t accounted_for = used * size + offset;
|
||||||
|
|
||||||
|
if (is_full())
|
||||||
|
{
|
||||||
|
// All the blocks must be used.
|
||||||
|
assert(SLAB_SIZE == accounted_for);
|
||||||
|
// There is no free list to validate
|
||||||
|
// 'link' value is not important if full.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Block is not full
|
||||||
|
assert(SLAB_SIZE > accounted_for);
|
||||||
|
|
||||||
|
// Walk bump-free-list-segment accounting for unused space
|
||||||
|
uint16_t curr = head;
|
||||||
|
while ((curr & 1) != 1)
|
||||||
|
{
|
||||||
|
// Check we are looking at a correctly aligned block
|
||||||
|
assert((curr - offset) % size == 0);
|
||||||
|
// Account for free elements in free list
|
||||||
|
accounted_for += size;
|
||||||
|
assert(SLAB_SIZE >= accounted_for);
|
||||||
|
// We are not guaranteed to hit a bump ptr unless
|
||||||
|
// we are the top element on the size class, so treat as
|
||||||
|
// a list segment.
|
||||||
|
if (curr == link)
|
||||||
|
break;
|
||||||
|
// Iterate bump/free list segment
|
||||||
|
curr = *(uint16_t*)((uintptr_t)slab + curr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we terminated traversal on a correctly aligned block
|
||||||
|
assert(((curr & ~1) - offset) % size == 0);
|
||||||
|
|
||||||
|
if (curr != link)
|
||||||
|
{
|
||||||
|
// The link should be at the special end location as we
|
||||||
|
// haven't completely filled this block at any point.
|
||||||
|
assert(link == SLABLINK_INDEX);
|
||||||
|
// Account for to be bump allocated space
|
||||||
|
accounted_for += SLAB_SIZE - (curr - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All space accounted for
|
||||||
|
assert(SLAB_SIZE == accounted_for);
|
||||||
|
#else
|
||||||
|
UNUSED(slab);
|
||||||
|
UNUSED(is_short);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
227
src/mem/pagemap.h
Normal file
227
src/mem/pagemap.h
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<size_t GRANULARITY_BITS, typename T, T default_content>
|
||||||
|
class Pagemap
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static constexpr size_t PAGEMAP_BITS = 16;
|
||||||
|
static constexpr size_t PAGEMAP_SIZE = 1 << PAGEMAP_BITS;
|
||||||
|
|
||||||
|
static constexpr size_t COVERED_BITS =
|
||||||
|
bits::ADDRESS_BITS - GRANULARITY_BITS;
|
||||||
|
static constexpr size_t POINTER_BITS =
|
||||||
|
bits::next_pow2_bits_const(sizeof(void*));
|
||||||
|
static constexpr size_t CONTENT_BITS =
|
||||||
|
bits::next_pow2_bits_const(sizeof(T));
|
||||||
|
|
||||||
|
static constexpr size_t BITS_FOR_LEAF = PAGEMAP_BITS - CONTENT_BITS;
|
||||||
|
static constexpr size_t ENTRIES_PER_LEAF = 1 << BITS_FOR_LEAF;
|
||||||
|
static constexpr size_t LEAF_MASK = ENTRIES_PER_LEAF - 1;
|
||||||
|
|
||||||
|
static constexpr size_t BITS_PER_INDEX_LEVEL = PAGEMAP_BITS - POINTER_BITS;
|
||||||
|
static constexpr size_t ENTRIES_PER_INDEX_LEVEL = 1 << BITS_PER_INDEX_LEVEL;
|
||||||
|
static constexpr size_t ENTRIES_MASK = ENTRIES_PER_INDEX_LEVEL - 1;
|
||||||
|
|
||||||
|
static constexpr size_t INDEX_BITS =
|
||||||
|
BITS_FOR_LEAF > COVERED_BITS ? 0 : COVERED_BITS - BITS_FOR_LEAF;
|
||||||
|
|
||||||
|
static constexpr size_t INDEX_LEVELS = INDEX_BITS / BITS_PER_INDEX_LEVEL;
|
||||||
|
static constexpr size_t TOPLEVEL_BITS =
|
||||||
|
INDEX_BITS - (INDEX_LEVELS * BITS_PER_INDEX_LEVEL);
|
||||||
|
static constexpr size_t TOPLEVEL_ENTRIES = 1 << TOPLEVEL_BITS;
|
||||||
|
static constexpr size_t TOPLEVEL_SHIFT =
|
||||||
|
(INDEX_LEVELS * BITS_PER_INDEX_LEVEL) + BITS_FOR_LEAF + GRANULARITY_BITS;
|
||||||
|
|
||||||
|
// Value used to represent when a node is being added too
|
||||||
|
static constexpr uintptr_t LOCKED_ENTRY = 1;
|
||||||
|
|
||||||
|
struct Leaf
|
||||||
|
{
|
||||||
|
std::atomic<T> values[ENTRIES_PER_LEAF];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PagemapEntry
|
||||||
|
{
|
||||||
|
std::atomic<PagemapEntry*> entries[ENTRIES_PER_INDEX_LEVEL];
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
sizeof(PagemapEntry) == sizeof(Leaf), "Should be the same size");
|
||||||
|
|
||||||
|
// Init removed as not required as this is only ever a global
|
||||||
|
// cl is generating a memset of zero, which will be a problem
|
||||||
|
// in libc/ucrt bring up. On ucrt this will run after the first
|
||||||
|
// allocation.
|
||||||
|
// TODO: This is fragile that it is not being memset, and we should review
|
||||||
|
// to ensure we don't get bitten by this in the future.
|
||||||
|
std::atomic<PagemapEntry*> top[TOPLEVEL_ENTRIES]; // = {nullptr};
|
||||||
|
|
||||||
|
template<bool create_addr>
|
||||||
|
inline PagemapEntry* get_node(std::atomic<PagemapEntry*>* e, bool& result)
|
||||||
|
{
|
||||||
|
// The page map nodes are all allocated directly from the OS zero
|
||||||
|
// initialised with a system call. We don't need any ordered to guarantee
|
||||||
|
// to see that correctly.
|
||||||
|
PagemapEntry* value = e->load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if ((uintptr_t)value <= LOCKED_ENTRY)
|
||||||
|
{
|
||||||
|
if constexpr (create_addr)
|
||||||
|
{
|
||||||
|
value = nullptr;
|
||||||
|
|
||||||
|
if (e->compare_exchange_strong(
|
||||||
|
value, (PagemapEntry*)LOCKED_ENTRY, std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
auto& v = default_memory_provider;
|
||||||
|
value = (PagemapEntry*)v.alloc_chunk(PAGEMAP_SIZE);
|
||||||
|
e->store(value, std::memory_order_release);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while ((uintptr_t)e->load(std::memory_order_relaxed) ==
|
||||||
|
LOCKED_ENTRY)
|
||||||
|
{
|
||||||
|
bits::pause();
|
||||||
|
}
|
||||||
|
value = e->load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = false;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = true;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool create_addr>
|
||||||
|
inline std::pair<Leaf*, size_t> get_leaf_index(void* p, bool& result)
|
||||||
|
{
|
||||||
|
size_t addr = (size_t)p;
|
||||||
|
#ifdef FreeBSD_KERNEL
|
||||||
|
// Zero the top 16 bits - kernel addresses all have them set, but the
|
||||||
|
// data structure assumes that they're zero.
|
||||||
|
addr &= 0xffffffffffffULL;
|
||||||
|
#endif
|
||||||
|
size_t ix = addr >> TOPLEVEL_SHIFT;
|
||||||
|
size_t shift = TOPLEVEL_SHIFT;
|
||||||
|
std::atomic<PagemapEntry*>* e = &top[ix];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < INDEX_LEVELS; i++)
|
||||||
|
{
|
||||||
|
PagemapEntry* value = get_node<create_addr>(e, result);
|
||||||
|
if (!result)
|
||||||
|
return std::pair(nullptr, 0);
|
||||||
|
|
||||||
|
shift -= BITS_PER_INDEX_LEVEL;
|
||||||
|
ix = (addr >> shift) & ENTRIES_MASK;
|
||||||
|
e = &value->entries[ix];
|
||||||
|
|
||||||
|
if constexpr (INDEX_LEVELS == 1)
|
||||||
|
{
|
||||||
|
UNUSED(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i == INDEX_LEVELS)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Leaf* leaf = (Leaf*)get_node<create_addr>(e, result);
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
return std::pair(nullptr, 0);
|
||||||
|
|
||||||
|
shift -= BITS_FOR_LEAF;
|
||||||
|
ix = (addr >> shift) & LEAF_MASK;
|
||||||
|
return std::pair(leaf, ix);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool create_addr>
|
||||||
|
inline std::atomic<T>* get_addr(void* p, bool& success)
|
||||||
|
{
|
||||||
|
auto leaf_ix = get_leaf_index<create_addr>(p, success);
|
||||||
|
return &(leaf_ix.first->values[leaf_ix.second]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr size_t GRANULARITY = 1 << GRANULARITY_BITS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of a pagemap entry within a given page. This is used
|
||||||
|
* in code that propagates changes to the pagemap elsewhere.
|
||||||
|
*/
|
||||||
|
size_t index_for_address(void* p)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
return (OS_PAGE_SIZE - 1) &
|
||||||
|
reinterpret_cast<size_t>(get_addr<true>(p, success));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the address of the page containing
|
||||||
|
*/
|
||||||
|
void* page_for_address(void* p)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
return reinterpret_cast<void*>(
|
||||||
|
~(OS_PAGE_SIZE - 1) &
|
||||||
|
reinterpret_cast<uintptr_t>(get_addr<true>(p, success)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::atomic<T>* get_ptr(void* p)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
return get_addr<true>(p, success);
|
||||||
|
}
|
||||||
|
|
||||||
|
T get(void* p)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
auto addr = get_addr<false>(p, success);
|
||||||
|
if (!success)
|
||||||
|
return default_content;
|
||||||
|
return addr->load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(void* p, T x)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
auto addr = get_addr<true>(p, success);
|
||||||
|
addr->store(x, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_range(void* p, T x, size_t length)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto leaf_ix = get_leaf_index<true>(p, success);
|
||||||
|
size_t ix = leaf_ix.second;
|
||||||
|
|
||||||
|
auto last = std::min(LEAF_MASK + 1, ix + length);
|
||||||
|
|
||||||
|
auto diff = last - ix;
|
||||||
|
|
||||||
|
for (; ix < last; ix++)
|
||||||
|
{
|
||||||
|
leaf_ix.first->values[ix] = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
length = length - diff;
|
||||||
|
p = (void*)((uintptr_t)p + (diff << GRANULARITY_BITS));
|
||||||
|
} while (length > 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
64
src/mem/remoteallocator.h
Normal file
64
src/mem/remoteallocator.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/mpscq.h"
|
||||||
|
#include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
struct Remote
|
||||||
|
{
|
||||||
|
static const uint64_t SIZECLASS_SHIFT = 56;
|
||||||
|
static const uint64_t SIZECLASS_MASK = 0xffULL << SIZECLASS_SHIFT;
|
||||||
|
static const uint64_t TARGET_MASK = ~SIZECLASS_MASK;
|
||||||
|
|
||||||
|
static_assert(SIZECLASS_MASK == 0xff00'0000'0000'0000ULL);
|
||||||
|
|
||||||
|
using alloc_id_t = size_t;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
std::atomic<Remote*> next;
|
||||||
|
Remote* non_atomic_next;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t value;
|
||||||
|
|
||||||
|
void set_target_id(alloc_id_t id)
|
||||||
|
{
|
||||||
|
assert(id == (id & TARGET_MASK));
|
||||||
|
value = (id & TARGET_MASK) | (value & SIZECLASS_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_sizeclass(uint8_t sizeclass)
|
||||||
|
{
|
||||||
|
value = (value & TARGET_MASK) |
|
||||||
|
((static_cast<uint64_t>(sizeclass) << SIZECLASS_SHIFT) &
|
||||||
|
SIZECLASS_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc_id_t target_id()
|
||||||
|
{
|
||||||
|
return value & TARGET_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sizeclass()
|
||||||
|
{
|
||||||
|
return (value & SIZECLASS_MASK) >> SIZECLASS_SHIFT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RemoteAllocator
|
||||||
|
{
|
||||||
|
using alloc_id_t = Remote::alloc_id_t;
|
||||||
|
// Store the message queue on a separate cacheline. It is mutable data that
|
||||||
|
// is read by other threads.
|
||||||
|
alignas(CACHELINE_SIZE) MPSCQ<Remote> message_queue;
|
||||||
|
|
||||||
|
alloc_id_t id()
|
||||||
|
{
|
||||||
|
return static_cast<alloc_id_t>(
|
||||||
|
reinterpret_cast<uintptr_t>(&message_queue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
148
src/mem/sizeclass.h
Normal file
148
src/mem/sizeclass.h
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "allocconfig.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
constexpr static uint16_t get_slab_offset(uint8_t sc, bool is_short);
|
||||||
|
constexpr static size_t sizeclass_to_size(uint8_t sizeclass);
|
||||||
|
constexpr static uint16_t medium_slab_free(uint8_t sizeclass);
|
||||||
|
|
||||||
|
static inline uint8_t size_to_sizeclass(size_t size)
|
||||||
|
{
|
||||||
|
// Don't use sizeclasses that are not a multiple of the alignment.
|
||||||
|
// For example, 24 byte allocations can be
|
||||||
|
// problematic for some data due to alignment issues.
|
||||||
|
return (uint8_t)bits::to_exp_mant<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static inline uint8_t size_to_sizeclass_const(size_t size)
|
||||||
|
{
|
||||||
|
// Don't use sizeclasses that are not a multiple of the alignment.
|
||||||
|
// For example, 24 byte allocations can be
|
||||||
|
// problematic for some data due to alignment issues.
|
||||||
|
return (uint8_t)bits::to_exp_mant_const<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(
|
||||||
|
size);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static inline size_t large_sizeclass_to_size(uint8_t large_class)
|
||||||
|
{
|
||||||
|
return (size_t)1 << (large_class + SUPERSLAB_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small classes range from [MIN, SLAB], i.e. inclusive.
|
||||||
|
static constexpr size_t NUM_SMALL_CLASSES =
|
||||||
|
size_to_sizeclass_const((size_t)1 << SLAB_BITS) + 1;
|
||||||
|
|
||||||
|
static constexpr size_t NUM_SIZECLASSES =
|
||||||
|
size_to_sizeclass_const((size_t)1 << SUPERSLAB_BITS);
|
||||||
|
|
||||||
|
// Medium classes range from (SLAB, SUPERSLAB), i.e. non-inclusive.
|
||||||
|
static constexpr size_t NUM_MEDIUM_CLASSES =
|
||||||
|
NUM_SIZECLASSES - NUM_SMALL_CLASSES;
|
||||||
|
|
||||||
|
// Large classes range from [SUPERSLAB, ADDRESS_SPACE).
|
||||||
|
static constexpr size_t NUM_LARGE_CLASSES =
|
||||||
|
bits::ADDRESS_BITS - SUPERSLAB_BITS;
|
||||||
|
|
||||||
|
template<size_t X, size_t Y>
|
||||||
|
constexpr void check_same()
|
||||||
|
{
|
||||||
|
static_assert(X == Y, "Values must be the same");
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(size_to_sizeclass_const(SUPERSLAB_SIZE) == NUM_SIZECLASSES);
|
||||||
|
|
||||||
|
inline static size_t round_by_sizeclass(size_t rsize, size_t offset)
|
||||||
|
{
|
||||||
|
// check_same<NUM_LARGE_CLASSES, Globals::num_large_classes>();
|
||||||
|
// Must be called with a rounded size.
|
||||||
|
assert(sizeclass_to_size(size_to_sizeclass(rsize)) == rsize);
|
||||||
|
// Only works up to certain offsets, exhaustively tested upto
|
||||||
|
// SUPERSLAB_SIZE.
|
||||||
|
assert(offset <= SUPERSLAB_SIZE);
|
||||||
|
|
||||||
|
size_t align = bits::ctz(rsize);
|
||||||
|
size_t divider = rsize >> align;
|
||||||
|
// Maximum of 24 bits for 16MiB super/medium slab
|
||||||
|
if (INTERMEDIATE_BITS == 0 || divider == 1)
|
||||||
|
{
|
||||||
|
assert(divider == 1);
|
||||||
|
return offset & ~(rsize - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (bits::is64() && INTERMEDIATE_BITS <= 2)
|
||||||
|
{
|
||||||
|
// Only works for 64 bit multiplication, as the following will overflow in
|
||||||
|
// 32bit.
|
||||||
|
// The code is using reciprocal division, with a shift of 26 bits, this
|
||||||
|
// is considerably more bits than we need in the result. If SUPERSLABS
|
||||||
|
// get larger then we should review this code.
|
||||||
|
static_assert(SUPERSLAB_BITS <= 24, "The following code assumes 24 bits");
|
||||||
|
static constexpr size_t shift = 26;
|
||||||
|
size_t back_shift = shift + align;
|
||||||
|
static constexpr size_t mul_shift = 1ULL << shift;
|
||||||
|
static constexpr uint32_t constants[8] = {0,
|
||||||
|
mul_shift,
|
||||||
|
0,
|
||||||
|
(mul_shift / 3) + 1,
|
||||||
|
0,
|
||||||
|
(mul_shift / 5) + 1,
|
||||||
|
0,
|
||||||
|
(mul_shift / 7) + 1};
|
||||||
|
return ((constants[divider] * offset) >> back_shift) * rsize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Use 32-bit division as considerably faster than 64-bit, and
|
||||||
|
// everything fits into 32bits here.
|
||||||
|
return (uint32_t)(offset / rsize) * rsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool is_multiple_of_sizeclass(size_t rsize, size_t offset)
|
||||||
|
{
|
||||||
|
// Must be called with a rounded size.
|
||||||
|
assert(sizeclass_to_size(size_to_sizeclass(rsize)) == rsize);
|
||||||
|
// Only works up to certain offsets, exhaustively tested upto
|
||||||
|
// SUPERSLAB_SIZE.
|
||||||
|
assert(offset <= SUPERSLAB_SIZE);
|
||||||
|
|
||||||
|
size_t align = bits::ctz(rsize);
|
||||||
|
size_t divider = rsize >> align;
|
||||||
|
// Maximum of 24 bits for 16MiB super/medium slab
|
||||||
|
if (INTERMEDIATE_BITS == 0 || divider == 1)
|
||||||
|
{
|
||||||
|
assert(divider == 1);
|
||||||
|
return (offset & (rsize - 1)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (bits::is64() && INTERMEDIATE_BITS <= 2)
|
||||||
|
{
|
||||||
|
// Only works for 64 bit multiplication, as the following will overflow in
|
||||||
|
// 32bit.
|
||||||
|
// The code is using reciprocal division, with a shift of 26 bits, this
|
||||||
|
// is considerably more bits than we need in the result. If SUPERSLABS
|
||||||
|
// get larger then we should review this code.
|
||||||
|
static_assert(SUPERSLAB_BITS <= 24, "The following code assumes 24 bits");
|
||||||
|
static constexpr size_t shift = 31;
|
||||||
|
static constexpr size_t mul_shift = 1ULL << shift;
|
||||||
|
static constexpr uint32_t constants[8] = {0,
|
||||||
|
mul_shift,
|
||||||
|
0,
|
||||||
|
(mul_shift / 3) + 1,
|
||||||
|
0,
|
||||||
|
(mul_shift / 5) + 1,
|
||||||
|
0,
|
||||||
|
(mul_shift / 7) + 1};
|
||||||
|
|
||||||
|
// There is a long chain of zeros after the backshift
|
||||||
|
// However, not all zero so just check a range.
|
||||||
|
// This is exhaustively tested for the current use case
|
||||||
|
return (((constants[divider] * offset)) &
|
||||||
|
(((1ULL << (align + 3)) - 1) << (shift - 3))) == 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Use 32-bit division as considerably faster than 64-bit, and
|
||||||
|
// everything fits into 32bits here.
|
||||||
|
return (uint32_t)(offset % rsize) == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
71
src/mem/sizeclasstable.h
Normal file
71
src/mem/sizeclasstable.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "superslab.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
struct SizeClassTable
|
||||||
|
{
|
||||||
|
size_t size[NUM_SIZECLASSES];
|
||||||
|
uint16_t bump_ptr_start[NUM_SMALL_CLASSES];
|
||||||
|
uint16_t short_bump_ptr_start[NUM_SMALL_CLASSES];
|
||||||
|
uint16_t count_per_slab[NUM_SMALL_CLASSES];
|
||||||
|
uint16_t medium_slab_slots[NUM_MEDIUM_CLASSES];
|
||||||
|
|
||||||
|
constexpr SizeClassTable()
|
||||||
|
: size(),
|
||||||
|
bump_ptr_start(),
|
||||||
|
short_bump_ptr_start(),
|
||||||
|
count_per_slab(),
|
||||||
|
medium_slab_slots()
|
||||||
|
{
|
||||||
|
for (uint8_t sizeclass = 0; sizeclass < NUM_SIZECLASSES; sizeclass++)
|
||||||
|
{
|
||||||
|
size[sizeclass] =
|
||||||
|
bits::from_exp_mant<INTERMEDIATE_BITS, MIN_ALLOC_BITS>(sizeclass);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t header_size = sizeof(Superslab);
|
||||||
|
size_t short_slab_size = SLAB_SIZE - header_size;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < NUM_SMALL_CLASSES; i++)
|
||||||
|
{
|
||||||
|
short_bump_ptr_start[i] =
|
||||||
|
(uint16_t)(1 + (short_slab_size % size[i]) + header_size);
|
||||||
|
bump_ptr_start[i] = (uint16_t)(1 + (SLAB_SIZE % size[i]));
|
||||||
|
count_per_slab[i] = (uint16_t)(SLAB_SIZE / size[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = NUM_SMALL_CLASSES; i < NUM_SIZECLASSES; i++)
|
||||||
|
{
|
||||||
|
medium_slab_slots[i - NUM_SMALL_CLASSES] =
|
||||||
|
(uint16_t)((SUPERSLAB_SIZE - Mediumslab::header_size()) / size[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr SizeClassTable sizeclass_metadata = SizeClassTable();
|
||||||
|
|
||||||
|
static inline constexpr uint16_t get_slab_offset(uint8_t sc, bool is_short)
|
||||||
|
{
|
||||||
|
if (is_short)
|
||||||
|
return sizeclass_metadata.short_bump_ptr_start[sc];
|
||||||
|
else
|
||||||
|
return sizeclass_metadata.bump_ptr_start[sc];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static inline size_t sizeclass_to_size(uint8_t sizeclass)
|
||||||
|
{
|
||||||
|
return sizeclass_metadata.size[sizeclass];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static inline size_t sizeclass_to_count(uint8_t sizeclass)
|
||||||
|
{
|
||||||
|
return sizeclass_metadata.count_per_slab[sizeclass];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static inline uint16_t medium_slab_free(uint8_t sizeclass)
|
||||||
|
{
|
||||||
|
return sizeclass_metadata.medium_slab_slots[sizeclass - NUM_SMALL_CLASSES];
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/mem/slab.h
Normal file
165
src/mem/slab.h
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "superslab.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Slab
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint16_t pointer_to_index(void* p)
|
||||||
|
{
|
||||||
|
// Get the offset from the slab for a memory location.
|
||||||
|
return (uint16_t)((size_t)p - (size_t)this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static Slab* get(void* p)
|
||||||
|
{
|
||||||
|
return (Slab*)((size_t)p & SLAB_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
Metaslab* get_meta()
|
||||||
|
{
|
||||||
|
Superslab* super = Superslab::get(this);
|
||||||
|
return super->get_meta(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
SlabLink* get_link()
|
||||||
|
{
|
||||||
|
return get_meta()->get_link(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ZeroMem zero_mem, typename MemoryProvider>
|
||||||
|
void* alloc(SlabList* sc, size_t rsize, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
// Read the head from the metadata stored in the superslab.
|
||||||
|
Metaslab* meta = get_meta();
|
||||||
|
uint16_t head = meta->head;
|
||||||
|
|
||||||
|
assert(rsize == sizeclass_to_size(meta->sizeclass));
|
||||||
|
meta->debug_slab_invariant(is_short(), this);
|
||||||
|
assert(sc->get_head() == (SlabLink*)((size_t)this + meta->link));
|
||||||
|
assert(!meta->is_full());
|
||||||
|
|
||||||
|
meta->add_use();
|
||||||
|
|
||||||
|
void* p;
|
||||||
|
|
||||||
|
if ((head & 1) == 0)
|
||||||
|
{
|
||||||
|
p = (void*)((size_t)this + head);
|
||||||
|
|
||||||
|
// Read the next slot from the memory that's about to be allocated.
|
||||||
|
uint16_t next = *(uint16_t*)p;
|
||||||
|
meta->head = next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// This slab is being bump allocated.
|
||||||
|
p = (void*)((size_t)this + head - 1);
|
||||||
|
meta->head = head + (uint16_t)rsize;
|
||||||
|
if (meta->head == 1)
|
||||||
|
{
|
||||||
|
meta->set_full();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're full, we're no longer the current slab for this sizeclass
|
||||||
|
if (meta->is_full())
|
||||||
|
sc->pop();
|
||||||
|
|
||||||
|
meta->debug_slab_invariant(is_short(), this);
|
||||||
|
|
||||||
|
if (zero_mem == YesZero)
|
||||||
|
{
|
||||||
|
if (rsize < PAGE_ALIGNED_SIZE)
|
||||||
|
memory_provider.zero(p, rsize);
|
||||||
|
else
|
||||||
|
memory_provider.template zero<true>(p, rsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true, if it alters get_status.
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
inline typename Superslab::Action dealloc(
|
||||||
|
SlabList* sc, Superslab* super, void* p, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
Metaslab* meta = super->get_meta(this);
|
||||||
|
|
||||||
|
bool was_full = meta->is_full();
|
||||||
|
meta->debug_slab_invariant(is_short(), this);
|
||||||
|
meta->sub_use();
|
||||||
|
|
||||||
|
#ifndef SNMALLOC_SAFE_CLIENT
|
||||||
|
if (!is_multiple_of_sizeclass(
|
||||||
|
sizeclass_to_size(meta->sizeclass),
|
||||||
|
(uintptr_t)this + SLAB_SIZE - (uintptr_t)p))
|
||||||
|
{
|
||||||
|
error("Not deallocating start of an object");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (was_full)
|
||||||
|
{
|
||||||
|
// We are not on the sizeclass list.
|
||||||
|
if (!meta->is_unused())
|
||||||
|
{
|
||||||
|
// Update the head and the sizeclass link.
|
||||||
|
uint16_t index = pointer_to_index(p);
|
||||||
|
meta->head = index;
|
||||||
|
assert(meta->valid_head(is_short()));
|
||||||
|
meta->link = index;
|
||||||
|
|
||||||
|
// Push on the list of slabs for this sizeclass.
|
||||||
|
sc->insert(meta->get_link(this));
|
||||||
|
meta->debug_slab_invariant(is_short(), this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Dealloc on the superslab.
|
||||||
|
if (is_short())
|
||||||
|
return super->dealloc_short_slab(memory_provider);
|
||||||
|
else
|
||||||
|
return super->dealloc_slab(this, memory_provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (meta->is_unused())
|
||||||
|
{
|
||||||
|
// Remove from the sizeclass list and dealloc on the superslab.
|
||||||
|
sc->remove(meta->get_link(this));
|
||||||
|
|
||||||
|
if (is_short())
|
||||||
|
return super->dealloc_short_slab(memory_provider);
|
||||||
|
else
|
||||||
|
return super->dealloc_slab(this, memory_provider);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
sc->debug_check_contains(meta->get_link(this));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Update the head and the next pointer in the free list.
|
||||||
|
uint16_t head = meta->head;
|
||||||
|
uint16_t current = pointer_to_index(p);
|
||||||
|
|
||||||
|
// Set the head to the memory being deallocated.
|
||||||
|
meta->head = current;
|
||||||
|
assert(meta->valid_head(is_short()));
|
||||||
|
|
||||||
|
// Set the next pointer to the previous head.
|
||||||
|
*(uint16_t*)p = head;
|
||||||
|
meta->debug_slab_invariant(is_short(), this);
|
||||||
|
}
|
||||||
|
return Superslab::NoSlabReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_short()
|
||||||
|
{
|
||||||
|
return ((size_t)this & SUPERSLAB_MASK) == (size_t)this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
233
src/mem/superslab.h
Normal file
233
src/mem/superslab.h
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "metaslab.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class Superslab : public Allocslab
|
||||||
|
{
|
||||||
|
// This is the view of a 16 mb superslab when it is being used to allocate
|
||||||
|
// 64 kb slabs.
|
||||||
|
private:
|
||||||
|
friend DLList<Superslab>;
|
||||||
|
|
||||||
|
// Keep the allocator pointer on a separate cache line. It is read by
|
||||||
|
// other threads, and does not change, so we avoid false sharing.
|
||||||
|
alignas(CACHELINE_SIZE)
|
||||||
|
// The superslab is kept on a doubly linked list of superslabs which
|
||||||
|
// have some space.
|
||||||
|
Superslab* next;
|
||||||
|
Superslab* prev;
|
||||||
|
|
||||||
|
// This is a reference to the first unused slab in the free slab list
|
||||||
|
// It is does not contain the short slab, which is handled using a bit
|
||||||
|
// in the "used" field below. The list is terminated by pointing to
|
||||||
|
// the short slab.
|
||||||
|
// The head linked list has an absolute pointer for head, but the next
|
||||||
|
// pointers stores in the metaslabs are relative pointers, that is they
|
||||||
|
// are the relative offset to the next entry minus 1. This means that
|
||||||
|
// all zeros is a list that chains through all the blocks, so the zero
|
||||||
|
// initialised memory requires no more work.
|
||||||
|
uint8_t head;
|
||||||
|
|
||||||
|
// Represents twice the number of full size slabs used
|
||||||
|
// plus 1 for the short slab. i.e. using 3 slabs and the
|
||||||
|
// short slab would be 6 + 1 = 7
|
||||||
|
uint16_t used;
|
||||||
|
|
||||||
|
Metaslab meta[SLAB_COUNT];
|
||||||
|
|
||||||
|
// Used size_t as results in better code in MSVC
|
||||||
|
size_t slab_to_index(Slab* slab)
|
||||||
|
{
|
||||||
|
auto res = (((size_t)slab - (size_t)this) >> SLAB_BITS);
|
||||||
|
assert(res == (uint8_t)res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Status
|
||||||
|
{
|
||||||
|
Full,
|
||||||
|
Available,
|
||||||
|
OnlyShortSlabAvailable,
|
||||||
|
Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Action
|
||||||
|
{
|
||||||
|
NoSlabReturn = 0,
|
||||||
|
NoStatusChange = 1,
|
||||||
|
StatusChange = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
static Superslab* get(void* p)
|
||||||
|
{
|
||||||
|
return (Superslab*)((size_t)p & SUPERSLAB_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_short_sizeclass(uint8_t sizeclass)
|
||||||
|
{
|
||||||
|
constexpr uint8_t h = size_to_sizeclass_const(sizeof(Superslab));
|
||||||
|
return sizeclass <= h;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
void init(RemoteAllocator* alloc, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
allocator = alloc;
|
||||||
|
|
||||||
|
if (kind != Super)
|
||||||
|
{
|
||||||
|
// If this wasn't previously a Superslab, we need to set up the
|
||||||
|
// header.
|
||||||
|
kind = Super;
|
||||||
|
// Point head at the first non-short slab.
|
||||||
|
head = 1;
|
||||||
|
|
||||||
|
if (kind != Fresh)
|
||||||
|
{
|
||||||
|
// If this wasn't previously Fresh, we need to zero some things.
|
||||||
|
used = 0;
|
||||||
|
memory_provider.zero(meta, SLAB_COUNT * sizeof(Metaslab));
|
||||||
|
}
|
||||||
|
|
||||||
|
meta[0].set_unused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_empty()
|
||||||
|
{
|
||||||
|
return used == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_full()
|
||||||
|
{
|
||||||
|
return (used == (((SLAB_COUNT - 1) << 1) + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_almost_full()
|
||||||
|
{
|
||||||
|
return (used >= ((SLAB_COUNT - 1) << 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
Status get_status()
|
||||||
|
{
|
||||||
|
if (!is_almost_full())
|
||||||
|
{
|
||||||
|
if (!is_empty())
|
||||||
|
{
|
||||||
|
return Available;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!is_full())
|
||||||
|
{
|
||||||
|
return OnlyShortSlabAvailable;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Metaslab* get_meta(Slab* slab)
|
||||||
|
{
|
||||||
|
return &meta[slab_to_index(slab)];
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
Slab* alloc_short_slab(uint8_t sizeclass, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
if ((used & 1) == 1)
|
||||||
|
return alloc_slab(sizeclass, memory_provider);
|
||||||
|
|
||||||
|
meta[0].head = get_slab_offset(sizeclass, true);
|
||||||
|
meta[0].sizeclass = sizeclass;
|
||||||
|
meta[0].link = SLABLINK_INDEX;
|
||||||
|
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
{
|
||||||
|
memory_provider.template notify_using<NoZero>(
|
||||||
|
(void*)((size_t)this + OS_PAGE_SIZE), SLAB_SIZE - OS_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
used++;
|
||||||
|
return (Slab*)this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
Slab* alloc_slab(uint8_t sizeclass, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
Slab* slab = (Slab*)((size_t)this + ((size_t)head << SLAB_BITS));
|
||||||
|
|
||||||
|
uint8_t n = meta[head].next;
|
||||||
|
|
||||||
|
meta[head].head = get_slab_offset(sizeclass, false);
|
||||||
|
meta[head].sizeclass = sizeclass;
|
||||||
|
meta[head].link = SLABLINK_INDEX;
|
||||||
|
|
||||||
|
head = head + n + 1;
|
||||||
|
used += 2;
|
||||||
|
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
{
|
||||||
|
memory_provider.template notify_using<NoZero>(slab, SLAB_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true, if this alters the value of get_status
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
Action dealloc_slab(Slab* slab, MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
// This is not the short slab.
|
||||||
|
uint8_t index = (uint8_t)slab_to_index(slab);
|
||||||
|
uint8_t n = head - index - 1;
|
||||||
|
|
||||||
|
meta[index].next = n;
|
||||||
|
head = index;
|
||||||
|
bool was_almost_full = is_almost_full();
|
||||||
|
used -= 2;
|
||||||
|
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
memory_provider.notify_not_using(slab, SLAB_SIZE);
|
||||||
|
|
||||||
|
assert(meta[index].is_unused());
|
||||||
|
if (was_almost_full || is_empty())
|
||||||
|
return StatusChange;
|
||||||
|
|
||||||
|
return NoStatusChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns true, if this alters the value of get_status
|
||||||
|
template<typename MemoryProvider>
|
||||||
|
Action dealloc_short_slab(MemoryProvider& memory_provider)
|
||||||
|
{
|
||||||
|
// This is the short slab.
|
||||||
|
if (decommit_strategy == DecommitAll)
|
||||||
|
{
|
||||||
|
memory_provider.notify_not_using(
|
||||||
|
(void*)((size_t)this + OS_PAGE_SIZE), SLAB_SIZE - OS_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool was_full = is_full();
|
||||||
|
used--;
|
||||||
|
|
||||||
|
assert(meta[0].is_unused());
|
||||||
|
if (was_full || is_empty())
|
||||||
|
return StatusChange;
|
||||||
|
|
||||||
|
return NoStatusChange;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
265
src/mem/threadalloc.h
Normal file
265
src/mem/threadalloc.h
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/helpers.h"
|
||||||
|
#include "globalalloc.h"
|
||||||
|
#if defined(SNMALLOC_USE_THREAD_DESTRUCTOR) && \
|
||||||
|
defined(SNMALLOC_USE_THREAD_CLEANUP)
|
||||||
|
#error At most one out of SNMALLOC_USE_THREAD_CLEANUP and SNMALLOC_USE_THREAD_DESTRUCTOR may be defined.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
extern "C" void _malloc_thread_cleanup(void);
|
||||||
|
|
||||||
|
#ifdef SNMALLOC_EXTERNAL_THREAD_ALLOC
|
||||||
|
/**
|
||||||
|
* Version of the `ThreadAlloc` interface that does no managment of thread
|
||||||
|
* local state, and just assumes that "ThreadAllocUntyped::get" has been
|
||||||
|
* declared before including snmalloc.h. As it is included before, it cannot
|
||||||
|
* know the allocator type, hence the casting.
|
||||||
|
*
|
||||||
|
* This class is used only when snmalloc is compiled as part of a runtime,
|
||||||
|
* which has its own managment of the thread local allocator pointer.
|
||||||
|
*/
|
||||||
|
class ThreadAllocUntypedWrapper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static inline Alloc*& get()
|
||||||
|
{
|
||||||
|
return (Alloc*&)ThreadAllocUntyped::get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version of the `ThreadAlloc` interface that uses a hook provided by libc
|
||||||
|
* to destroy thread-local state. This is the ideal option, because it
|
||||||
|
* enforces ordering of destruction such that the malloc state is destroyed
|
||||||
|
* after anything that can allocate memory.
|
||||||
|
*
|
||||||
|
* This class is used only when snmalloc is compiled as part of a compatible
|
||||||
|
* libc (for example, FreeBSD libc).
|
||||||
|
*/
|
||||||
|
class ThreadAllocLibcCleanup
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Libc will call `_malloc_thread_cleanup` just before a thread terminates.
|
||||||
|
* This function must be allowed to call back into this class to destroy
|
||||||
|
* the state.
|
||||||
|
*/
|
||||||
|
friend void _malloc_thread_cleanup(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when the thread exits. This is guaranteed to be called
|
||||||
|
* precisely once per thread and releases the current allocator.
|
||||||
|
*/
|
||||||
|
static inline void exit()
|
||||||
|
{
|
||||||
|
if (auto* per_thread = get(false))
|
||||||
|
{
|
||||||
|
current_alloc_pool()->release(per_thread);
|
||||||
|
per_thread = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Returns a pointer to the allocator associated with this thread. If
|
||||||
|
* `create` is true, it will create an allocator if one does not exist,
|
||||||
|
* otherwise it will return `nullptr` in this case. This should be called
|
||||||
|
* with `create == false` only during thread teardown.
|
||||||
|
*
|
||||||
|
* The non-create case exists so that the `per_thread` variable can be a
|
||||||
|
* local static and not a global, allowing ODR to deduplicate it.
|
||||||
|
*/
|
||||||
|
static inline Alloc*& get(bool create = true)
|
||||||
|
{
|
||||||
|
static thread_local Alloc* per_thread;
|
||||||
|
if (!per_thread && create)
|
||||||
|
{
|
||||||
|
per_thread = current_alloc_pool()->acquire();
|
||||||
|
}
|
||||||
|
return per_thread;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Version of the `ThreadAlloc` interface that uses C++ `thread_local`
|
||||||
|
* destructors for cleanup. If a per-thread allocator is used during the
|
||||||
|
* destruction of other per-thread data, this class will create a new
|
||||||
|
* instance and register its destructor, so should eventually result in
|
||||||
|
* cleanup, but may result in allocators being returned to the global pool
|
||||||
|
* and then reacquired multiple times.
|
||||||
|
*
|
||||||
|
* This implementation depends on nothing outside of a working C++
|
||||||
|
* environment and so should be the simplest for initial bringup on an
|
||||||
|
* unsupported platform. It is currently used in the FreeBSD kernel version.
|
||||||
|
*/
|
||||||
|
class ThreadAllocThreadDestructor
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A pointer to the allocator owned by this thread.
|
||||||
|
*/
|
||||||
|
Alloc* alloc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor. Acquires a new allocator and associates it with this
|
||||||
|
* object. There should be only one instance of this class per thread.
|
||||||
|
*/
|
||||||
|
ThreadAllocThreadDestructor() : alloc(current_alloc_pool()->acquire()) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destructor. Releases the allocator owned by this thread.
|
||||||
|
*/
|
||||||
|
~ThreadAllocThreadDestructor()
|
||||||
|
{
|
||||||
|
current_alloc_pool()->release(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Public interface, returns the allocator for this thread, constructing
|
||||||
|
* one if necessary.
|
||||||
|
*/
|
||||||
|
static inline Alloc*& get()
|
||||||
|
{
|
||||||
|
static thread_local ThreadAllocThreadDestructor per_thread;
|
||||||
|
return per_thread.alloc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// When targeting the FreeBSD kernel, the pthread header exists, but the
|
||||||
|
// pthread symbols do not, so don't compile this because it will fail to
|
||||||
|
// link.
|
||||||
|
#ifndef FreeBSD_KERNEL
|
||||||
|
/**
|
||||||
|
* Version of the `ThreadAlloc` interface that uses thread-specific (POSIX
|
||||||
|
* threads) or Fiber-local (Windows) storage with an explicit destructor.
|
||||||
|
* Neither of the underlying mechanisms guarantee ordering, so the cleanup
|
||||||
|
* may be called before other cleanup functions or thread-local destructors.
|
||||||
|
*
|
||||||
|
* This implementation is used when using snmalloc as a library
|
||||||
|
* implementation of malloc, but not embedding it in C standard library.
|
||||||
|
* Using this implementation removes the dependency on a C++ runtime library.
|
||||||
|
*/
|
||||||
|
class ThreadAllocExplicitTLSCleanup
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Cleanup function. This is registered with the operating system's
|
||||||
|
* thread- or fibre-local storage subsystem to clean up the per-thread
|
||||||
|
* allocator.
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
# ifdef _WIN32
|
||||||
|
NTAPI
|
||||||
|
# endif
|
||||||
|
thread_alloc_release(void* p)
|
||||||
|
{
|
||||||
|
Alloc** pp = (Alloc**)p;
|
||||||
|
current_alloc_pool()->release(*pp);
|
||||||
|
*pp = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ifdef _WIN32
|
||||||
|
/**
|
||||||
|
* Key type used to identify fibre-local storage.
|
||||||
|
*/
|
||||||
|
using tls_key_t = DWORD;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Windows, construct a new fibre-local storage allocation. This
|
||||||
|
* function must not be called more than once.
|
||||||
|
*/
|
||||||
|
static inline tls_key_t tls_key_create() noexcept
|
||||||
|
{
|
||||||
|
return FlsAlloc(thread_alloc_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Windows, store a pointer to a `thread_local` pointer to an allocator
|
||||||
|
* into fibre-local storage. This function takes a pointer to the
|
||||||
|
* `thread_local` allocation, rather than to the pointee, so that the
|
||||||
|
* cleanup function can zero the pointer.
|
||||||
|
*
|
||||||
|
* This must not be called until after `tls_key_create` has returned.
|
||||||
|
*/
|
||||||
|
static inline void tls_set_value(tls_key_t key, Alloc** value)
|
||||||
|
{
|
||||||
|
FlsSetValue(key, static_cast<void*>(value));
|
||||||
|
}
|
||||||
|
# else
|
||||||
|
/**
|
||||||
|
* Key type used for thread-specific storage.
|
||||||
|
*/
|
||||||
|
using tls_key_t = pthread_key_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On POSIX systems, construct a new thread-specific storage allocation.
|
||||||
|
* This function must not be called more than once.
|
||||||
|
*/
|
||||||
|
static inline tls_key_t tls_key_create() noexcept
|
||||||
|
{
|
||||||
|
tls_key_t key;
|
||||||
|
pthread_key_create(&key, thread_alloc_release);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On POSIX systems, store a pointer to a `thread_local` pointer to an
|
||||||
|
* allocator into fibre-local storage. This function takes a pointer to
|
||||||
|
* the `thread_local` allocation, rather than to the pointee, so that the
|
||||||
|
* cleanup function can zero the pointer.
|
||||||
|
*
|
||||||
|
* This must not be called until after `tls_key_create` has returned.
|
||||||
|
*/
|
||||||
|
static inline void tls_set_value(tls_key_t key, Alloc** value)
|
||||||
|
{
|
||||||
|
pthread_setspecific(key, static_cast<void*>(value));
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Public interface, returns the allocator for the current thread,
|
||||||
|
* constructing it if necessary.
|
||||||
|
*/
|
||||||
|
static inline Alloc*& get()
|
||||||
|
{
|
||||||
|
static thread_local Alloc* per_thread;
|
||||||
|
|
||||||
|
// If we don't have an allocator, construct one.
|
||||||
|
if (!per_thread)
|
||||||
|
{
|
||||||
|
// Construct the allocator and assign it to `per_thread` *before* doing
|
||||||
|
// anything else. This is important because `tls_key_create` may
|
||||||
|
// allocate memory and if we are providing the `malloc` implementation
|
||||||
|
// then this function must be re-entrant within a single thread. In
|
||||||
|
// this case, the second call to this function will simply return the
|
||||||
|
// allocator.
|
||||||
|
per_thread = current_alloc_pool()->acquire();
|
||||||
|
|
||||||
|
tls_key_t key = Singleton<tls_key_t, tls_key_create>::get();
|
||||||
|
// Associate the new allocator with the destructor.
|
||||||
|
tls_set_value(key, &per_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return per_thread;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SNMALLOC_USE_THREAD_CLEANUP
|
||||||
|
/**
|
||||||
|
* Entry point the allows libc to call into the allocator for per-thread
|
||||||
|
* cleanup.
|
||||||
|
*/
|
||||||
|
extern "C" void _malloc_thread_cleanup(void)
|
||||||
|
{
|
||||||
|
ThreadAllocLibcCleanup::exit();
|
||||||
|
}
|
||||||
|
using ThreadAlloc = ThreadAllocLibcCleanup;
|
||||||
|
#elif defined(SNMALLOC_USE_THREAD_DESTRUCTOR)
|
||||||
|
using ThreadAlloc = ThreadAllocThreadDestructor;
|
||||||
|
#elif defined(SNMALLOC_EXTERNAL_THREAD_ALLOC)
|
||||||
|
using ThreadAlloc = ThreadAllocUntypedWrapper;
|
||||||
|
#else
|
||||||
|
using ThreadAlloc = ThreadAllocExplicitTLSCleanup;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
86
src/mem/typealloc.h
Normal file
86
src/mem/typealloc.h
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/flaglock.h"
|
||||||
|
#include "../ds/mpmcstack.h"
|
||||||
|
#include "typeallocated.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class T, class MemoryProvider = GlobalVirtual>
|
||||||
|
class TypeAlloc
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend TypeAllocated<T>;
|
||||||
|
|
||||||
|
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||||
|
MPMCStack<T, PreZeroed> stack;
|
||||||
|
T* list = nullptr;
|
||||||
|
|
||||||
|
TypeAlloc(MemoryProvider& m) : memory_provider(m) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemoryProvider& memory_provider;
|
||||||
|
|
||||||
|
static TypeAlloc* make(MemoryProvider& memory_provider) noexcept
|
||||||
|
{
|
||||||
|
auto r = memory_provider.alloc_chunk(sizeof(TypeAlloc));
|
||||||
|
return new (r) TypeAlloc(memory_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TypeAlloc* make() noexcept
|
||||||
|
{
|
||||||
|
return make(default_memory_provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
T* alloc(Args&&... args)
|
||||||
|
{
|
||||||
|
T* p = stack.pop();
|
||||||
|
|
||||||
|
if (p != nullptr)
|
||||||
|
return p;
|
||||||
|
|
||||||
|
p = (T*)memory_provider.alloc_chunk(sizeof(T));
|
||||||
|
|
||||||
|
new (p) T(std::forward<Args...>(args)...);
|
||||||
|
|
||||||
|
FlagLock f(lock);
|
||||||
|
p->list_next = list;
|
||||||
|
list = p;
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dealloc(T* p)
|
||||||
|
{
|
||||||
|
// The object's destructor is not run. If the object is "reallocated", it
|
||||||
|
// is returned without the constructor being run, so the object is reused
|
||||||
|
// without re-initialisation.
|
||||||
|
stack.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* extract(T* p = nullptr)
|
||||||
|
{
|
||||||
|
// Returns a linked list of all objects in the stack, emptying the stack.
|
||||||
|
if (p == nullptr)
|
||||||
|
return stack.pop_all();
|
||||||
|
else
|
||||||
|
return p->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore(T* first, T* last)
|
||||||
|
{
|
||||||
|
// Pushes a linked list of objects onto the stack. Use to put a linked
|
||||||
|
// list returned by extract back onto the stack.
|
||||||
|
stack.push(first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* iterate(T* p = nullptr)
|
||||||
|
{
|
||||||
|
if (p == nullptr)
|
||||||
|
return list;
|
||||||
|
else
|
||||||
|
return p->list_next;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
19
src/mem/typeallocated.h
Normal file
19
src/mem/typeallocated.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
template<class T>
|
||||||
|
class TypeAllocated
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
template<class TT, class MemoryProvider>
|
||||||
|
friend class TypeAlloc;
|
||||||
|
template<class TT, Construction c>
|
||||||
|
friend class MPMCStack;
|
||||||
|
|
||||||
|
std::atomic<T*> next = nullptr;
|
||||||
|
T* list_next;
|
||||||
|
};
|
||||||
|
}
|
||||||
188
src/override/malloc.cc
Normal file
188
src/override/malloc.cc
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
#include "../snmalloc.h"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
#ifndef SNMALLOC_NAME_MANGLE
|
||||||
|
# define SNMALLOC_NAME_MANGLE(a) a
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
void* SNMALLOC_NAME_MANGLE(__malloc_end_pointer)(void* ptr)
|
||||||
|
{
|
||||||
|
return Alloc::external_pointer<End>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(malloc)(size_t size)
|
||||||
|
{
|
||||||
|
// Include size 0 in the first sizeclass.
|
||||||
|
size = ((size - 1) >> (bits::BITS - 1)) + size;
|
||||||
|
|
||||||
|
return ThreadAlloc::get()->alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SNMALLOC_NAME_MANGLE(free)(void* ptr)
|
||||||
|
{
|
||||||
|
if (ptr == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ThreadAlloc::get()->dealloc(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(calloc)(size_t nmemb, size_t size)
|
||||||
|
{
|
||||||
|
bool overflow = false;
|
||||||
|
size_t sz = bits::umul(size, nmemb, overflow);
|
||||||
|
if (overflow)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// Include size 0 in the first sizeclass.
|
||||||
|
sz = ((sz - 1) >> (bits::BITS - 1)) + sz;
|
||||||
|
return ThreadAlloc::get()->alloc<ZeroMem::YesZero>(sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SNMALLOC_NAME_MANGLE(malloc_usable_size)(void* ptr)
|
||||||
|
{
|
||||||
|
return Alloc::alloc_size(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(realloc)(void* ptr, size_t size)
|
||||||
|
{
|
||||||
|
if (size == (size_t)-1)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (ptr == nullptr)
|
||||||
|
{
|
||||||
|
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||||
|
}
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// This check is redundant, because the check in memcpy will fail if this
|
||||||
|
// is skipped, but it's useful for debugging.
|
||||||
|
if (Alloc::external_pointer<Start>(ptr) != ptr)
|
||||||
|
{
|
||||||
|
error(
|
||||||
|
"Calling realloc on pointer that is not to the start of an allocation");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
void* p = SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||||
|
if (p)
|
||||||
|
{
|
||||||
|
assert(p == Alloc::external_pointer<Start>(p));
|
||||||
|
size_t sz =
|
||||||
|
(std::min)(size, SNMALLOC_NAME_MANGLE(malloc_usable_size)(ptr));
|
||||||
|
memcpy(p, ptr, sz);
|
||||||
|
SNMALLOC_NAME_MANGLE(free)(ptr);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __FreeBSD__
|
||||||
|
void* SNMALLOC_NAME_MANGLE(reallocarray)(void* ptr, size_t nmemb, size_t size)
|
||||||
|
{
|
||||||
|
bool overflow = false;
|
||||||
|
size_t sz = bits::umul(size, nmemb, overflow);
|
||||||
|
if (overflow)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return SNMALLOC_NAME_MANGLE(realloc)(ptr, sz);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(aligned_alloc)(size_t alignment, size_t size)
|
||||||
|
{
|
||||||
|
assert((size % alignment) == 0);
|
||||||
|
(void)alignment;
|
||||||
|
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(memalign)(size_t alignment, size_t size)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(alignment == 0) || (alignment == size_t(-1)) ||
|
||||||
|
(alignment > SUPERSLAB_SIZE))
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if ((size + alignment) < size)
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sc = size_to_sizeclass((std::max)(size, alignment));
|
||||||
|
if (sc >= NUM_SIZECLASSES)
|
||||||
|
{
|
||||||
|
// large allocs are 16M aligned.
|
||||||
|
return SNMALLOC_NAME_MANGLE(malloc)(size);
|
||||||
|
}
|
||||||
|
for (; sc < NUM_SIZECLASSES; sc++)
|
||||||
|
{
|
||||||
|
size = sizeclass_to_size(sc);
|
||||||
|
if ((size & -size) >= alignment)
|
||||||
|
{
|
||||||
|
return SNMALLOC_NAME_MANGLE(aligned_alloc)(alignment, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SNMALLOC_NAME_MANGLE(posix_memalign)(
|
||||||
|
void** memptr, size_t alignment, size_t size)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
((alignment % sizeof(void*)) != 0) ||
|
||||||
|
((alignment & (alignment - 1)) != 0) || (alignment == 0))
|
||||||
|
{
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* p = SNMALLOC_NAME_MANGLE(memalign)(alignment, size);
|
||||||
|
if (p == nullptr)
|
||||||
|
{
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
*memptr = p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __FreeBSD__
|
||||||
|
void* SNMALLOC_NAME_MANGLE(valloc)(size_t size)
|
||||||
|
{
|
||||||
|
return SNMALLOC_NAME_MANGLE(memalign)(OS_PAGE_SIZE, size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void* SNMALLOC_NAME_MANGLE(pvalloc)(size_t size)
|
||||||
|
{
|
||||||
|
if (size == size_t(-1))
|
||||||
|
{
|
||||||
|
errno = ENOMEM;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return SNMALLOC_NAME_MANGLE(memalign)(
|
||||||
|
OS_PAGE_SIZE, (size + OS_PAGE_SIZE - 1) & ~(OS_PAGE_SIZE - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SNMALLOC_NAME_MANGLE(_malloc_prefork)(void) {}
|
||||||
|
void SNMALLOC_NAME_MANGLE(_malloc_postfork)(void) {}
|
||||||
|
void SNMALLOC_NAME_MANGLE(_malloc_first_thread)(void) {}
|
||||||
|
int SNMALLOC_NAME_MANGLE(mallctl)(const char*, void*, size_t*, void*, size_t)
|
||||||
|
{
|
||||||
|
return ENOENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/override/new.cc
Normal file
67
src/override/new.cc
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include "../mem/alloc.h"
|
||||||
|
#include "../mem/threadalloc.h"
|
||||||
|
#include "../snmalloc.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define EXCEPTSPEC
|
||||||
|
#else
|
||||||
|
# ifdef _GLIBCXX_USE_NOEXCEPT
|
||||||
|
# define EXCEPTSPEC _GLIBCXX_USE_NOEXCEPT
|
||||||
|
# elif defined(_NOEXCEPT)
|
||||||
|
# define EXCEPTSPEC _NOEXCEPT
|
||||||
|
# else
|
||||||
|
# define EXCEPTSPEC
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
void* operator new(size_t size)
|
||||||
|
{
|
||||||
|
return ThreadAlloc::get()->alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new[](size_t size)
|
||||||
|
{
|
||||||
|
return ThreadAlloc::get()->alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new(size_t size, std::nothrow_t&)
|
||||||
|
{
|
||||||
|
return ThreadAlloc::get()->alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new[](size_t size, std::nothrow_t&)
|
||||||
|
{
|
||||||
|
return ThreadAlloc::get()->alloc(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete(void* p)EXCEPTSPEC
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete(void* p, size_t size)EXCEPTSPEC
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete(void* p, std::nothrow_t&)
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* p) EXCEPTSPEC
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* p, size_t size) EXCEPTSPEC
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator delete[](void* p, std::nothrow_t&)
|
||||||
|
{
|
||||||
|
ThreadAlloc::get()->dealloc(p);
|
||||||
|
}
|
||||||
46
src/pal/pal.h
Normal file
46
src/pal/pal.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
void error(const char* const str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If simultating OE, then we need the underlying platform
|
||||||
|
#if !defined(OPEN_ENCLAVE) || defined(OPEN_ENCLAVE_SIMULATION)
|
||||||
|
# include "pal_free_bsd_kernel.h"
|
||||||
|
# include "pal_freebsd.h"
|
||||||
|
# include "pal_linux.h"
|
||||||
|
# include "pal_windows.h"
|
||||||
|
#endif
|
||||||
|
#include "pal_open_enclave.h"
|
||||||
|
#include "pal_plain.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
#if !defined(OPEN_ENCLAVE) || defined(OPEN_ENCLAVE_SIMULATION)
|
||||||
|
using DefaultPal =
|
||||||
|
# if defined(_WIN32)
|
||||||
|
PALWindows;
|
||||||
|
# elif defined(__linux__)
|
||||||
|
PALLinux;
|
||||||
|
# elif defined(FreeBSD_KERNEL)
|
||||||
|
PALFreeBSDKernel;
|
||||||
|
# elif defined(__FreeBSD__)
|
||||||
|
PALFBSD;
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using Pal =
|
||||||
|
#ifdef OPEN_ENCLAVE
|
||||||
|
PALPlainMixin<PALOpenEnclave>;
|
||||||
|
#elif defined(SNMALLOC_MEMORY_PROVIDER)
|
||||||
|
PALPlainMixin<SNMALLOC_MEMORY_PROVIDER>
|
||||||
|
#else
|
||||||
|
DefaultPal;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline void error(const char* const str)
|
||||||
|
{
|
||||||
|
Pal::error(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/pal/pal_free_bsd_kernel.h
Normal file
90
src/pal/pal_free_bsd_kernel.h
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
#include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
#if defined(FreeBSD_KERNEL)
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
# include <sys/vmem.h>
|
||||||
|
# include <vm/vm.h>
|
||||||
|
# include <vm/vm_extern.h>
|
||||||
|
# include <vm/vm_kern.h>
|
||||||
|
# include <vm/vm_object.h>
|
||||||
|
# include <vm/vm_param.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class PALFreeBSDKernel
|
||||||
|
{
|
||||||
|
vm_offset_t get_vm_offset(uint_ptr_t p)
|
||||||
|
{
|
||||||
|
return static_cast<vm_offset_t>(reinterpret_cast<uintptr_t>(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void error(const char* const str)
|
||||||
|
{
|
||||||
|
panic("snmalloc error: %s", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
void notify_not_using(void* p, size_t size)
|
||||||
|
{
|
||||||
|
vm_offset_t addr = get_vm_offset(p);
|
||||||
|
kmem_unback(kernel_object, addr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void notify_using(void* p, size_t size)
|
||||||
|
{
|
||||||
|
vm_offset_t addr = get_vm_offset(p);
|
||||||
|
int flags = M_WAITOK | ((zero_mem == YesZero) ? M_ZERO : 0);
|
||||||
|
if (kmem_back(kernel_object, addr, size, flags) != KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
error("Out of memory");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OS specific function for zeroing memory
|
||||||
|
template<bool page_aligned = false>
|
||||||
|
void zero(void* p, size_t size)
|
||||||
|
{
|
||||||
|
::bzero(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool committed>
|
||||||
|
void* reserve(size_t* size, size_t align)
|
||||||
|
{
|
||||||
|
size_t request = *size;
|
||||||
|
vm_offset_t addr;
|
||||||
|
if (vmem_xalloc(
|
||||||
|
kernel_arena,
|
||||||
|
request,
|
||||||
|
align,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VMEM_ADDR_MIN,
|
||||||
|
VMEM_ADDR_MAX,
|
||||||
|
M_BESTFIT,
|
||||||
|
&addr))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (committed)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
kmem_back(kernel_object, addr, request, M_ZERO | M_WAITOK) !=
|
||||||
|
KERN_SUCCESS)
|
||||||
|
{
|
||||||
|
vmem_xfree(kernel_arena, addr, request);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return get_vm_offset(addr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
88
src/pal/pal_freebsd.h
Normal file
88
src/pal/pal_freebsd.h
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__FreeBSD__) && !defined(_KERNEL)
|
||||||
|
# include "../ds/bits.h"
|
||||||
|
# include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
# include <sys/mman.h>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class PALFBSD
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void error(const char* const str)
|
||||||
|
{
|
||||||
|
puts(str);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
void notify_not_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
madvise(p, size, MADV_FREE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void notify_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||||
|
if (zero_mem == YesZero)
|
||||||
|
zero(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OS specific function for zeroing memory
|
||||||
|
template<bool page_aligned = false>
|
||||||
|
void zero(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
void* r = mmap(
|
||||||
|
p,
|
||||||
|
size,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (r != MAP_FAILED)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bzero(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool committed>
|
||||||
|
void* reserve(size_t* size, size_t align) noexcept
|
||||||
|
{
|
||||||
|
size_t request = *size;
|
||||||
|
// Alignment must be a power of 2.
|
||||||
|
assert(align == bits::next_pow2(align));
|
||||||
|
|
||||||
|
if (align == 0)
|
||||||
|
{
|
||||||
|
align = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t log2align = bits::next_pow2_bits(align);
|
||||||
|
|
||||||
|
void* p = mmap(
|
||||||
|
NULL,
|
||||||
|
request,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS | MAP_ALIGNED(log2align),
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (p == MAP_FAILED)
|
||||||
|
error("Out of memory");
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
93
src/pal/pal_linux.h
Normal file
93
src/pal/pal_linux.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(__linux__)
|
||||||
|
# include "../ds/bits.h"
|
||||||
|
# include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
# include <stdio.h>
|
||||||
|
# include <string.h>
|
||||||
|
# include <sys/mman.h>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class PALLinux
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void error(const char* const str)
|
||||||
|
{
|
||||||
|
puts(str);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
void notify_not_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
// Do nothing. Don't call madvise here, as the system call slows the
|
||||||
|
// allocator down too much.
|
||||||
|
UNUSED(p);
|
||||||
|
UNUSED(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void notify_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||||
|
|
||||||
|
if (zero_mem == YesZero)
|
||||||
|
zero<true>(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OS specific function for zeroing memory
|
||||||
|
template<bool page_aligned = false>
|
||||||
|
void zero(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
madvise(p, size, MADV_DONTNEED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
::memset(p, 0, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool committed>
|
||||||
|
void* reserve(size_t* size, size_t align) noexcept
|
||||||
|
{
|
||||||
|
size_t request = *size;
|
||||||
|
// Add align, so we can guarantee to provide at least size.
|
||||||
|
request += align;
|
||||||
|
// Alignment must be a power of 2.
|
||||||
|
assert(align == bits::next_pow2(align));
|
||||||
|
|
||||||
|
void* p = mmap(
|
||||||
|
NULL,
|
||||||
|
request,
|
||||||
|
PROT_READ | PROT_WRITE,
|
||||||
|
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||||
|
-1,
|
||||||
|
0);
|
||||||
|
|
||||||
|
if (p == MAP_FAILED)
|
||||||
|
error("Out of memory");
|
||||||
|
*size = request;
|
||||||
|
uintptr_t p0 = (uintptr_t)p;
|
||||||
|
uintptr_t start = bits::align_up(p0, align);
|
||||||
|
|
||||||
|
if (start > (uintptr_t)p0)
|
||||||
|
{
|
||||||
|
uintptr_t end = bits::align_down(p0 + request, align);
|
||||||
|
*size = end - start;
|
||||||
|
munmap(p, start - p0);
|
||||||
|
munmap((void*)end, (p0 + request) - end);
|
||||||
|
p = (void*)start;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
58
src/pal/pal_open_enclave.h
Normal file
58
src/pal/pal_open_enclave.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pal_plain.h"
|
||||||
|
#ifdef OPEN_ENCLAVE
|
||||||
|
extern "C" const void* __oe_get_heap_base();
|
||||||
|
extern "C" const void* __oe_get_heap_end();
|
||||||
|
extern "C" void* oe_memset(void* p, int c, size_t size);
|
||||||
|
extern "C" void oe_abort();
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class PALOpenEnclave
|
||||||
|
{
|
||||||
|
std::atomic<uintptr_t> oe_base;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void error(const char* const str)
|
||||||
|
{
|
||||||
|
UNUSED(str);
|
||||||
|
oe_abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool committed>
|
||||||
|
void* reserve(size_t* size, size_t align) noexcept
|
||||||
|
{
|
||||||
|
if (oe_base == 0)
|
||||||
|
{
|
||||||
|
uintptr_t dummy = 0;
|
||||||
|
oe_base.compare_exchange_strong(dummy, (uintptr_t)__oe_get_heap_base());
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t old_base = oe_base;
|
||||||
|
uintptr_t old_base2 = old_base;
|
||||||
|
uintptr_t next_base;
|
||||||
|
auto end = (uintptr_t)__oe_get_heap_end();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
old_base2 = old_base;
|
||||||
|
auto new_base = bits::align_up(old_base, align);
|
||||||
|
next_base = new_base + *size;
|
||||||
|
|
||||||
|
if (next_base > end)
|
||||||
|
error("Out of memory");
|
||||||
|
|
||||||
|
} while (oe_base.compare_exchange_strong(old_base, next_base));
|
||||||
|
|
||||||
|
*size = next_base - old_base2;
|
||||||
|
return (void*)old_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool page_aligned = false>
|
||||||
|
void zero(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
oe_memset(p, 0, size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
34
src/pal/pal_plain.h
Normal file
34
src/pal/pal_plain.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
#include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
// Can be extended
|
||||||
|
// Will require a reserve method in subclasses.
|
||||||
|
template<class State>
|
||||||
|
class PALPlainMixin : public State
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PALPlainMixin() : State() {}
|
||||||
|
|
||||||
|
// Notify platform that we will not be using these pages
|
||||||
|
void notify_not_using(void*, size_t) noexcept {}
|
||||||
|
|
||||||
|
// Notify platform that we will not be using these pages
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void notify_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
if constexpr (zero_mem == YesZero)
|
||||||
|
{
|
||||||
|
State::zero(p, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UNUSED(p);
|
||||||
|
UNUSED(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
111
src/pal/pal_windows.h
Normal file
111
src/pal/pal_windows.h
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../ds/bits.h"
|
||||||
|
#include "../mem/allocconfig.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
# define NOMINMAX
|
||||||
|
# include <windows.h>
|
||||||
|
|
||||||
|
namespace snmalloc
|
||||||
|
{
|
||||||
|
class PALWindows
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void error(const char* const str)
|
||||||
|
{
|
||||||
|
puts(str);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
void notify_not_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
|
||||||
|
BOOL ok = VirtualFree(p, size, MEM_DECOMMIT);
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
error("VirtualFree failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify platform that we will not be using these pages
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void notify_using(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
assert(
|
||||||
|
bits::is_aligned_block<OS_PAGE_SIZE>(p, size) || (zero_mem == NoZero));
|
||||||
|
|
||||||
|
void* r = VirtualAlloc(p, size, MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
|
||||||
|
if (r == nullptr)
|
||||||
|
error("out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// OS specific function for zeroing memory
|
||||||
|
template<bool page_aligned = false>
|
||||||
|
void zero(void* p, size_t size) noexcept
|
||||||
|
{
|
||||||
|
if (page_aligned || bits::is_aligned_block<OS_PAGE_SIZE>(p, size))
|
||||||
|
{
|
||||||
|
assert(bits::is_aligned_block<OS_PAGE_SIZE>(p, size));
|
||||||
|
notify_not_using(p, size);
|
||||||
|
notify_using<YesZero>(p, size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
::memset(p, 0, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
# ifdef USE_SYSTEMATIC_TESTING
|
||||||
|
size_t& systematic_bump_ptr()
|
||||||
|
{
|
||||||
|
static size_t bump_ptr = (size_t)0x4000'0000'0000;
|
||||||
|
return bump_ptr;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
template<bool committed>
|
||||||
|
void* reserve(size_t* size, size_t align) noexcept
|
||||||
|
{
|
||||||
|
// Add align, so we can guarantee to provide at least size.
|
||||||
|
size_t request = *size + align;
|
||||||
|
|
||||||
|
// Alignment must be a power of 2.
|
||||||
|
assert(align == bits::next_pow2(align));
|
||||||
|
|
||||||
|
DWORD flags = MEM_RESERVE;
|
||||||
|
|
||||||
|
if (committed)
|
||||||
|
flags |= MEM_COMMIT;
|
||||||
|
|
||||||
|
void* p;
|
||||||
|
# ifdef USE_SYSTEMATIC_TESTING
|
||||||
|
size_t retries = 1000;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
p = VirtualAlloc(
|
||||||
|
(void*)systematic_bump_ptr(), request, flags, PAGE_READWRITE);
|
||||||
|
|
||||||
|
systematic_bump_ptr() += request;
|
||||||
|
retries--;
|
||||||
|
} while (p == nullptr && retries > 0);
|
||||||
|
# else
|
||||||
|
p = VirtualAlloc(nullptr, request, flags, PAGE_READWRITE);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
uintptr_t aligned_p = bits::align_up((size_t)p, align);
|
||||||
|
|
||||||
|
if (aligned_p != (uintptr_t)p)
|
||||||
|
{
|
||||||
|
auto extra_bit = aligned_p - (uintptr_t)p;
|
||||||
|
uintptr_t end = (uintptr_t)p + request;
|
||||||
|
// Attempt to align end of the block.
|
||||||
|
VirtualAlloc((void*)end, extra_bit, flags, PAGE_READWRITE);
|
||||||
|
}
|
||||||
|
*size = request;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
3
src/snmalloc.h
Normal file
3
src/snmalloc.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "mem/threadalloc.h"
|
||||||
52
src/test/func/fixed_region/fixed_region.cc
Normal file
52
src/test/func/fixed_region/fixed_region.cc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
#define OPEN_ENCLAVE
|
||||||
|
#define OPEN_ENCLAVE_SIMULATION
|
||||||
|
#define USE_RESERVE_MULTIPLE 1
|
||||||
|
#include <iostream>
|
||||||
|
#include <snmalloc.h>
|
||||||
|
|
||||||
|
void* oe_base;
|
||||||
|
void* oe_end;
|
||||||
|
extern "C" const void* __oe_get_heap_base()
|
||||||
|
{
|
||||||
|
return oe_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" const void* __oe_get_heap_end()
|
||||||
|
{
|
||||||
|
return oe_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void* oe_memset(void* p, int c, size_t size)
|
||||||
|
{
|
||||||
|
return memset(p, c, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void oe_abort()
|
||||||
|
{
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
DefaultPal pal;
|
||||||
|
|
||||||
|
size_t size = 1ULL << 28;
|
||||||
|
oe_base = pal.reserve<true>(&size, 0);
|
||||||
|
oe_end = (uint8_t*)oe_base + size;
|
||||||
|
std::cout << "Allocated region " << oe_base << " - " << oe_end << std::endl;
|
||||||
|
|
||||||
|
auto a = ThreadAlloc::get();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 1000; i++)
|
||||||
|
{
|
||||||
|
auto r1 = a->alloc(100);
|
||||||
|
std::cout << "Allocated object " << r1 << std::endl;
|
||||||
|
|
||||||
|
if (oe_base > r1)
|
||||||
|
abort();
|
||||||
|
if (oe_end < r1)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
275
src/test/func/memory/memory.cc
Normal file
275
src/test/func/memory/memory.cc
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
#include <snmalloc.h>
|
||||||
|
#include <test/opt.h>
|
||||||
|
#include <test/xoroshiro.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
void test_alloc_dealloc_64k()
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
constexpr size_t count = 1 << 12;
|
||||||
|
constexpr size_t outer_count = 12;
|
||||||
|
void* garbage[count];
|
||||||
|
void* keep_alive[outer_count];
|
||||||
|
|
||||||
|
for (size_t j = 0; j < outer_count; j++)
|
||||||
|
{
|
||||||
|
// Allocate 64k of 16byte allocs
|
||||||
|
// This will fill the short slab, and then start a new slab.
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
garbage[i] = alloc->alloc(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate one object on the second slab
|
||||||
|
keep_alive[j] = alloc->alloc(16);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
alloc->dealloc(garbage[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (size_t j = 0; j < outer_count; j++)
|
||||||
|
{
|
||||||
|
alloc->dealloc(keep_alive[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_random_allocation()
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
std::unordered_set<void*> allocated;
|
||||||
|
|
||||||
|
constexpr size_t count = 10000;
|
||||||
|
constexpr size_t outer_count = 10;
|
||||||
|
void* objects[count];
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
objects[i] = nullptr;
|
||||||
|
|
||||||
|
// Randomly allocate and deallocate objects
|
||||||
|
xoroshiro::p128r32 r;
|
||||||
|
size_t alloc_count = 0;
|
||||||
|
for (size_t j = 0; j < outer_count; j++)
|
||||||
|
{
|
||||||
|
auto just_dealloc = r.next() % 2 == 1;
|
||||||
|
auto duration = r.next() % count;
|
||||||
|
for (size_t i = 0; i < duration; i++)
|
||||||
|
{
|
||||||
|
auto index = r.next();
|
||||||
|
auto& cell = objects[index % count];
|
||||||
|
if (cell != nullptr)
|
||||||
|
{
|
||||||
|
alloc->dealloc(cell);
|
||||||
|
allocated.erase(cell);
|
||||||
|
cell = nullptr;
|
||||||
|
alloc_count--;
|
||||||
|
}
|
||||||
|
if (!just_dealloc)
|
||||||
|
{
|
||||||
|
cell = alloc->alloc(16);
|
||||||
|
auto pair = allocated.insert(cell);
|
||||||
|
// Check not already allocated
|
||||||
|
assert(pair.second);
|
||||||
|
UNUSED(pair);
|
||||||
|
alloc_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (alloc_count == 0 && just_dealloc)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate all the remaining objects
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
if (objects[i] != nullptr)
|
||||||
|
alloc->dealloc(objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_calloc()
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
for (size_t size = 16; size <= (1 << 24); size <<= 1)
|
||||||
|
{
|
||||||
|
void* p = alloc->alloc(size);
|
||||||
|
memset(p, 0xFF, size);
|
||||||
|
alloc->dealloc(p, size);
|
||||||
|
|
||||||
|
p = alloc->alloc<YesZero>(size);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; i++)
|
||||||
|
{
|
||||||
|
if (((char*)p)[i] != 0)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc->dealloc(p, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_double_alloc()
|
||||||
|
{
|
||||||
|
auto* a1 = current_alloc_pool()->acquire();
|
||||||
|
auto* a2 = current_alloc_pool()->acquire();
|
||||||
|
|
||||||
|
const size_t n = (1 << 16) / 32;
|
||||||
|
|
||||||
|
for (size_t k = 0; k < 4; k++)
|
||||||
|
{
|
||||||
|
std::unordered_set<void*> set1;
|
||||||
|
std::unordered_set<void*> set2;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < (n * 2); i++)
|
||||||
|
{
|
||||||
|
void* p = a1->alloc(20);
|
||||||
|
assert(set1.find(p) == set1.end());
|
||||||
|
set1.insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < (n * 2); i++)
|
||||||
|
{
|
||||||
|
void* p = a2->alloc(20);
|
||||||
|
assert(set2.find(p) == set2.end());
|
||||||
|
set2.insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!set1.empty())
|
||||||
|
{
|
||||||
|
auto it = set1.begin();
|
||||||
|
a2->dealloc(*it, 20);
|
||||||
|
set1.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!set2.empty())
|
||||||
|
{
|
||||||
|
auto it = set2.begin();
|
||||||
|
a1->dealloc(*it, 20);
|
||||||
|
set2.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current_alloc_pool()->release(a1);
|
||||||
|
current_alloc_pool()->release(a2);
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_external_pointer()
|
||||||
|
{
|
||||||
|
// Malloc does not have an external pointer querying mechanism.
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
for (uint8_t sc = 0; sc < NUM_SIZECLASSES; sc++)
|
||||||
|
{
|
||||||
|
size_t size = sizeclass_to_size(sc);
|
||||||
|
void* p1 = alloc->alloc(size);
|
||||||
|
|
||||||
|
for (size_t offset = 0; offset < size; offset += 17)
|
||||||
|
{
|
||||||
|
void* p2 = (void*)((size_t)p1 + offset);
|
||||||
|
void* p3 = Alloc::external_pointer(p2);
|
||||||
|
void* p4 = Alloc::external_pointer<End>(p2);
|
||||||
|
UNUSED(p3);
|
||||||
|
UNUSED(p4);
|
||||||
|
assert(p1 == p3);
|
||||||
|
assert((size_t)p4 == (size_t)p1 + size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc->dealloc(p1, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
};
|
||||||
|
|
||||||
|
void check_offset(void* base, void* interior)
|
||||||
|
{
|
||||||
|
void* calced_base = Alloc::external_pointer((void*)interior);
|
||||||
|
if (calced_base != (void*)base)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_external_pointer_large(size_t* base)
|
||||||
|
{
|
||||||
|
size_t size = *base;
|
||||||
|
char* curr = (char*)base;
|
||||||
|
for (size_t offset = 0; offset < size; offset += 1 << 24)
|
||||||
|
{
|
||||||
|
check_offset(base, (void*)(curr + offset));
|
||||||
|
check_offset(base, (void*)(curr + offset + (1 << 24) - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_external_pointer_large()
|
||||||
|
{
|
||||||
|
xoroshiro::p128r64 r;
|
||||||
|
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
constexpr size_t count_log = 5;
|
||||||
|
constexpr size_t count = 1 << count_log;
|
||||||
|
// Pre allocate all the objects
|
||||||
|
size_t* objects[count];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
size_t rand = r.next() & ((1 << 28) - 1);
|
||||||
|
size_t size = (1 << 24) + rand;
|
||||||
|
// store object
|
||||||
|
objects[i] = (size_t*)alloc->alloc(size);
|
||||||
|
// Store allocators size for this object
|
||||||
|
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||||
|
|
||||||
|
check_external_pointer_large(objects[i]);
|
||||||
|
if (i > 0)
|
||||||
|
check_external_pointer_large(objects[i - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
check_external_pointer_large(objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deallocate everything
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
alloc->dealloc(objects[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_alloc_16M()
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
// sizes >= 16M use large_alloc
|
||||||
|
const size_t size = 16'000'000;
|
||||||
|
|
||||||
|
void* p1 = alloc->alloc(size);
|
||||||
|
assert(Alloc::alloc_size(Alloc::external_pointer(p1)) >= size);
|
||||||
|
alloc->dealloc(p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
#ifdef USE_SYSTEMATIC_TESTING
|
||||||
|
opt::Opt opt(argc, argv);
|
||||||
|
size_t seed = opt.is<size_t>("--seed", 0);
|
||||||
|
Virtual::systematic_bump_ptr() += seed << 17;
|
||||||
|
#else
|
||||||
|
UNUSED(argc);
|
||||||
|
UNUSED(argv);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
test_external_pointer_large();
|
||||||
|
test_alloc_dealloc_64k();
|
||||||
|
test_random_allocation();
|
||||||
|
test_calloc();
|
||||||
|
test_double_alloc();
|
||||||
|
test_external_pointer();
|
||||||
|
test_alloc_16M();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
36
src/test/func/rounding/rounding.cc
Normal file
36
src/test/func/rounding/rounding.cc
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include <snmalloc.h>
|
||||||
|
#include <test/opt.h>
|
||||||
|
#include <test/xoroshiro.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
// Check for all sizeclass that we correctly round every offset within
|
||||||
|
// a superslab to the correct value, by comparing with the standard
|
||||||
|
// unoptimised version using division.
|
||||||
|
// Also check we correctly determine multiples using optimized check.
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
UNUSED(argc);
|
||||||
|
UNUSED(argv);
|
||||||
|
|
||||||
|
for (size_t size_class = 0; size_class < NUM_SIZECLASSES; size_class++)
|
||||||
|
{
|
||||||
|
size_t rsize = sizeclass_to_size((uint8_t)size_class);
|
||||||
|
for (size_t offset = 0; offset < SUPERSLAB_SIZE; offset++)
|
||||||
|
{
|
||||||
|
size_t rounded = (offset / rsize) * rsize;
|
||||||
|
bool mod_0 = (offset % rsize) == 0;
|
||||||
|
|
||||||
|
size_t opt_rounded = round_by_sizeclass(rsize, offset);
|
||||||
|
if (rounded != opt_rounded)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
bool opt_mod_0 = is_multiple_of_sizeclass(rsize, offset);
|
||||||
|
if (opt_mod_0 != mod_0)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
249
src/test/histogram.h
Normal file
249
src/test/histogram.h
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef USE_MEASURE
|
||||||
|
# include "../ds/flaglock.h"
|
||||||
|
|
||||||
|
# include <algorithm>
|
||||||
|
# include <iomanip>
|
||||||
|
# include <iostream>
|
||||||
|
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers) \
|
||||||
|
static constexpr const char* const id##_time_markers[] = markers; \
|
||||||
|
static histogram::Global<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||||
|
id##_time_global(#id, __FILE__, __LINE__, id##_time_markers); \
|
||||||
|
static thread_local histogram::Histogram<uint64_t, minbits, maxbits> \
|
||||||
|
id##_time_local(id##_time_global); \
|
||||||
|
histogram::MeasureTime<histogram::Histogram<uint64_t, minbits, maxbits>> \
|
||||||
|
id##_time(id##_time_local);
|
||||||
|
|
||||||
|
# define MEASURE_TIME(id, minbits, maxbits) \
|
||||||
|
MEASURE_TIME_MARKERS(id, minbits, maxbits, {nullptr})
|
||||||
|
|
||||||
|
# define MARKERS(...) \
|
||||||
|
{ \
|
||||||
|
__VA_ARGS__, nullptr \
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace histogram
|
||||||
|
{
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
template<class H>
|
||||||
|
class Global;
|
||||||
|
|
||||||
|
template<
|
||||||
|
class V,
|
||||||
|
size_t LOW_BITS,
|
||||||
|
size_t HIGH_BITS,
|
||||||
|
size_t INTERMEDIATE_BITS = LOW_BITS>
|
||||||
|
class Histogram
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using This = Histogram<V, LOW_BITS, HIGH_BITS, INTERMEDIATE_BITS>;
|
||||||
|
friend Global<This>;
|
||||||
|
|
||||||
|
static_assert(LOW_BITS < HIGH_BITS, "LOW_BITS must be less than HIGH_BITS");
|
||||||
|
|
||||||
|
static constexpr V LOW = (V)((size_t)1 << LOW_BITS);
|
||||||
|
static constexpr V HIGH = (V)((size_t)1 << HIGH_BITS);
|
||||||
|
static constexpr size_t BUCKETS =
|
||||||
|
((HIGH_BITS - LOW_BITS) << INTERMEDIATE_BITS) + 2;
|
||||||
|
|
||||||
|
private:
|
||||||
|
V high = (std::numeric_limits<V>::min)();
|
||||||
|
size_t overflow;
|
||||||
|
size_t count[BUCKETS];
|
||||||
|
|
||||||
|
Global<This>* global;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Histogram() : global(nullptr) {}
|
||||||
|
Histogram(Global<This>& g) : global(&g) {}
|
||||||
|
|
||||||
|
~Histogram()
|
||||||
|
{
|
||||||
|
if (global != nullptr)
|
||||||
|
global->add(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void record(V value)
|
||||||
|
{
|
||||||
|
if (value > high)
|
||||||
|
high = value;
|
||||||
|
|
||||||
|
if (value >= HIGH)
|
||||||
|
{
|
||||||
|
overflow++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto i = get_index(value);
|
||||||
|
assert(i < BUCKETS);
|
||||||
|
count[i]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
V get_high()
|
||||||
|
{
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_overflow()
|
||||||
|
{
|
||||||
|
return overflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_buckets()
|
||||||
|
{
|
||||||
|
return BUCKETS;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t get_count(size_t index)
|
||||||
|
{
|
||||||
|
if (index >= BUCKETS)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return count[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::pair<V, V> get_range(size_t index)
|
||||||
|
{
|
||||||
|
if (index >= BUCKETS)
|
||||||
|
return std::make_pair(HIGH, HIGH);
|
||||||
|
|
||||||
|
if (index == 0)
|
||||||
|
return std::make_pair(0, get_value(index));
|
||||||
|
|
||||||
|
return std::make_pair(get_value(index - 1) + 1, get_value(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(This& that)
|
||||||
|
{
|
||||||
|
high = (std::max)(high, that.high);
|
||||||
|
overflow += that.overflow;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < BUCKETS; i++)
|
||||||
|
count[i] += that.count[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void print(std::ostream& o)
|
||||||
|
{
|
||||||
|
o << "\tHigh: " << high << std::endl
|
||||||
|
<< "\tOverflow: " << overflow << std::endl;
|
||||||
|
|
||||||
|
size_t grand_total = overflow;
|
||||||
|
for (size_t i = 0; i < BUCKETS; i++)
|
||||||
|
grand_total += count[i];
|
||||||
|
|
||||||
|
size_t old_percentage = 0;
|
||||||
|
size_t cumulative_total = 0;
|
||||||
|
for (size_t i = 0; i < BUCKETS; i++)
|
||||||
|
{
|
||||||
|
auto r = get_range(i);
|
||||||
|
|
||||||
|
cumulative_total += count[i];
|
||||||
|
|
||||||
|
o << "\t" << std::setfill(' ') << std::setw(6) << std::get<0>(r) << ".."
|
||||||
|
<< std::setfill(' ') << std::setw(6) << std::get<1>(r) << ": "
|
||||||
|
<< std::setfill(' ') << std::setw(10) << count[i];
|
||||||
|
|
||||||
|
auto percentage = (cumulative_total * 100 / grand_total);
|
||||||
|
if (percentage != old_percentage)
|
||||||
|
{
|
||||||
|
old_percentage = percentage;
|
||||||
|
o << std::setfill(' ') << std::setw(20)
|
||||||
|
<< (cumulative_total * 100 / grand_total) << "%";
|
||||||
|
}
|
||||||
|
|
||||||
|
o << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t get_index(V value)
|
||||||
|
{
|
||||||
|
return bits::to_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(
|
||||||
|
value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static V get_value(size_t index)
|
||||||
|
{
|
||||||
|
return bits::
|
||||||
|
from_exp_mant<INTERMEDIATE_BITS, LOW_BITS - INTERMEDIATE_BITS>(index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class H>
|
||||||
|
class Global
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const char* name;
|
||||||
|
const char* file;
|
||||||
|
size_t line;
|
||||||
|
const char* const* markers;
|
||||||
|
|
||||||
|
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||||
|
H aggregate;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Global(
|
||||||
|
const char* name_,
|
||||||
|
const char* file_,
|
||||||
|
size_t line_,
|
||||||
|
const char* const* markers)
|
||||||
|
: name(name_), file(file_), line(line_), markers(markers)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~Global()
|
||||||
|
{
|
||||||
|
print();
|
||||||
|
}
|
||||||
|
|
||||||
|
void add(H& histogram)
|
||||||
|
{
|
||||||
|
FlagLock f(lock);
|
||||||
|
aggregate.add(histogram);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void print()
|
||||||
|
{
|
||||||
|
std::cout << name;
|
||||||
|
|
||||||
|
if (markers != nullptr)
|
||||||
|
{
|
||||||
|
std::cout << ": ";
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
while (markers[i] != nullptr)
|
||||||
|
std::cout << markers[i++] << " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl << file << ":" << line << std::endl;
|
||||||
|
|
||||||
|
aggregate.print(std::cout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class H>
|
||||||
|
class MeasureTime
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
H& histogram;
|
||||||
|
uint64_t t;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MeasureTime(H& histogram_) : histogram(histogram_)
|
||||||
|
{
|
||||||
|
t = bits::benchmark_time_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
~MeasureTime()
|
||||||
|
{
|
||||||
|
histogram.record(bits::benchmark_time_end() - t);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
# define MEASURE_TIME(id, minbits, maxbits)
|
||||||
|
# define MEASURE_TIME_MARKERS(id, minbits, maxbits, markers)
|
||||||
|
#endif
|
||||||
14
src/test/measuretime.h
Normal file
14
src/test/measuretime.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#define DO_TIME(name, code) \
|
||||||
|
{ \
|
||||||
|
auto start__ = std::chrono::high_resolution_clock::now(); \
|
||||||
|
code auto finish__ = std::chrono::high_resolution_clock::now(); \
|
||||||
|
auto diff__ = finish__ - start__; \
|
||||||
|
std::cout << name << ": " << std::setw(12) << diff__.count() << " ns" \
|
||||||
|
<< std::endl; \
|
||||||
|
}
|
||||||
91
src/test/opt.h
Normal file
91
src/test/opt.h
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
namespace opt
|
||||||
|
{
|
||||||
|
class Opt
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Opt(int argc, char** argv) : argc(argc), argv(argv) {}
|
||||||
|
|
||||||
|
bool has(const char* opt)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
if (!strcmp(opt, argv[i]))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
T is(const char* opt, T def)
|
||||||
|
{
|
||||||
|
size_t len = strlen(opt);
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
const char* p = param(opt, len, i);
|
||||||
|
|
||||||
|
if (p != nullptr)
|
||||||
|
{
|
||||||
|
char* end = nullptr;
|
||||||
|
T r;
|
||||||
|
|
||||||
|
if (std::is_unsigned<T>::value)
|
||||||
|
r = (T)strtoull(p, &end, 10);
|
||||||
|
else
|
||||||
|
r = (T)strtoll(p, &end, 10);
|
||||||
|
|
||||||
|
if ((r == 0) && (end == p))
|
||||||
|
return def;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* is(const char* opt, const char* def)
|
||||||
|
{
|
||||||
|
size_t len = strlen(opt);
|
||||||
|
|
||||||
|
for (int i = 1; i < argc; i++)
|
||||||
|
{
|
||||||
|
const char* p = param(opt, len, i);
|
||||||
|
|
||||||
|
if (p != nullptr)
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* param(const char* opt, size_t len, int i)
|
||||||
|
{
|
||||||
|
if (strncmp(opt, argv[i], len))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
switch (argv[i][len])
|
||||||
|
{
|
||||||
|
case '\0':
|
||||||
|
return (i < (argc - 1)) ? argv[i + 1] : nullptr;
|
||||||
|
case '=':
|
||||||
|
return &argv[i][len + 1];
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
176
src/test/perf/contention/contention.cc
Normal file
176
src/test/perf/contention/contention.cc
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#include "test/measuretime.h"
|
||||||
|
#include "test/opt.h"
|
||||||
|
#include "test/usage.h"
|
||||||
|
#include "test/xoroshiro.h"
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <snmalloc.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
bool use_malloc = false;
|
||||||
|
|
||||||
|
template<void f(size_t id)>
|
||||||
|
class ParallelTest
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::atomic<bool> flag = false;
|
||||||
|
std::atomic<size_t> ready = 0;
|
||||||
|
uint64_t start;
|
||||||
|
uint64_t end;
|
||||||
|
std::atomic<size_t> complete = 0;
|
||||||
|
|
||||||
|
size_t cores;
|
||||||
|
|
||||||
|
void run(size_t id)
|
||||||
|
{
|
||||||
|
auto prev = ready.fetch_add(1);
|
||||||
|
if (prev + 1 == cores)
|
||||||
|
{
|
||||||
|
start = bits::tick();
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
while (!flag)
|
||||||
|
bits::pause();
|
||||||
|
|
||||||
|
f(id);
|
||||||
|
|
||||||
|
prev = complete.fetch_add(1);
|
||||||
|
if (prev + 1 == cores)
|
||||||
|
{
|
||||||
|
end = bits::tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
ParallelTest(size_t cores) : cores(cores)
|
||||||
|
{
|
||||||
|
std::thread* t = new std::thread[cores];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < cores; i++)
|
||||||
|
{
|
||||||
|
t[i] = std::thread(&ParallelTest::run, this, i);
|
||||||
|
}
|
||||||
|
// Wait for all the threads.
|
||||||
|
for (size_t i = 0; i < cores; i++)
|
||||||
|
{
|
||||||
|
t[i].join();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] t;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t time()
|
||||||
|
{
|
||||||
|
return end - start;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<size_t*>* contention;
|
||||||
|
size_t swapsize;
|
||||||
|
size_t swapcount;
|
||||||
|
|
||||||
|
void test_tasks_f(size_t id)
|
||||||
|
{
|
||||||
|
Alloc* a = ThreadAlloc::get();
|
||||||
|
xoroshiro::p128r32 r(id + 5000);
|
||||||
|
|
||||||
|
for (size_t n = 0; n < swapcount; n++)
|
||||||
|
{
|
||||||
|
size_t size = 16 + (r.next() % 1024);
|
||||||
|
size_t* res = (size_t*)(use_malloc ? malloc(size) : a->alloc(size));
|
||||||
|
|
||||||
|
*res = size;
|
||||||
|
size_t* out =
|
||||||
|
contention[n % swapsize].exchange(res, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (out != nullptr)
|
||||||
|
{
|
||||||
|
size = *out;
|
||||||
|
if (use_malloc)
|
||||||
|
free(out);
|
||||||
|
else
|
||||||
|
a->dealloc(out, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void test_tasks(size_t num_tasks, size_t count, size_t size)
|
||||||
|
{
|
||||||
|
Alloc* a = ThreadAlloc::get();
|
||||||
|
|
||||||
|
contention = new std::atomic<size_t*>[size];
|
||||||
|
xoroshiro::p128r32 r;
|
||||||
|
|
||||||
|
for (size_t n = 0; n < size; n++)
|
||||||
|
{
|
||||||
|
size_t alloc_size = 16 + (r.next() % 1024);
|
||||||
|
size_t* res =
|
||||||
|
(size_t*)(use_malloc ? malloc(alloc_size) : a->alloc(alloc_size));
|
||||||
|
*res = alloc_size;
|
||||||
|
contention[n] = res;
|
||||||
|
}
|
||||||
|
swapcount = count;
|
||||||
|
swapsize = size;
|
||||||
|
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
Stats s0;
|
||||||
|
current_alloc_pool()->aggregate_stats(s0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
ParallelTest<test_tasks_f> test(num_tasks);
|
||||||
|
|
||||||
|
std::cout << "Task test, " << num_tasks << " threads, " << count
|
||||||
|
<< " swaps per thread " << test.time() << "ticks" << std::endl;
|
||||||
|
|
||||||
|
for (size_t n = 0; n < swapsize; n++)
|
||||||
|
{
|
||||||
|
if (contention[n] != nullptr)
|
||||||
|
{
|
||||||
|
if (use_malloc)
|
||||||
|
free(contention[n]);
|
||||||
|
else
|
||||||
|
a->dealloc(contention[n], *contention[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete[] contention;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
opt::Opt opt(argc, argv);
|
||||||
|
size_t cores = opt.is<size_t>("--cores", 8);
|
||||||
|
|
||||||
|
size_t count = opt.is<size_t>("--swapcount", 1 << 20);
|
||||||
|
size_t size = opt.is<size_t>("--swapsize", 1 << 18);
|
||||||
|
use_malloc = opt.has("--use_malloc");
|
||||||
|
|
||||||
|
std::cout << "Allocator is " << (use_malloc ? "System" : "snmalloc")
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
for (size_t i = cores; i > 0; i >>= 1)
|
||||||
|
test_tasks(i, count, size);
|
||||||
|
|
||||||
|
if (opt.has("--stats"))
|
||||||
|
{
|
||||||
|
#ifdef USE_SNMALLOC_STATS
|
||||||
|
Stats s;
|
||||||
|
current_alloc_pool()->aggregate_stats(s);
|
||||||
|
s.print<Alloc>(std::cout);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
usage::print_memory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
78
src/test/perf/external_pointer/externalpointer.cc
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include <snmalloc.h>
|
||||||
|
#include <test/measuretime.h>
|
||||||
|
#include <test/xoroshiro.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
constexpr size_t count_log = 20;
|
||||||
|
constexpr size_t count = 1 << count_log;
|
||||||
|
// Pre allocate all the objects
|
||||||
|
size_t* objects[count];
|
||||||
|
|
||||||
|
NOINLINE void setup(xoroshiro::p128r64& r, Alloc* alloc)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
size_t rand = (size_t)r.next();
|
||||||
|
size_t offset = bits::clz(rand);
|
||||||
|
if (offset > 30)
|
||||||
|
offset = 30;
|
||||||
|
size_t size = (rand & 15) << offset;
|
||||||
|
if (size < 16)
|
||||||
|
size = 16;
|
||||||
|
// store object
|
||||||
|
objects[i] = (size_t*)alloc->alloc(size);
|
||||||
|
// Store allocators size for this object
|
||||||
|
*objects[i] = Alloc::alloc_size(objects[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE void teardown(Alloc* alloc)
|
||||||
|
{
|
||||||
|
// Deallocate everything
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
alloc->dealloc(objects[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_external_pointer(xoroshiro::p128r64& r)
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
setup(r, alloc);
|
||||||
|
|
||||||
|
DO_TIME("External pointer queries ", {
|
||||||
|
for (size_t i = 0; i < 10000000; i++)
|
||||||
|
{
|
||||||
|
size_t rand = (size_t)r.next();
|
||||||
|
size_t oid = rand & (((size_t)1 << count_log) - 1);
|
||||||
|
size_t* external_ptr = objects[oid];
|
||||||
|
size_t size = *external_ptr;
|
||||||
|
size_t offset = (size >> 4) * (rand & 15);
|
||||||
|
size_t interior_ptr = ((size_t)external_ptr) + offset;
|
||||||
|
void* calced_external = Alloc::external_pointer((void*)interior_ptr);
|
||||||
|
if (calced_external != external_ptr)
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
teardown(alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int, char**)
|
||||||
|
{
|
||||||
|
xoroshiro::p128r64 r;
|
||||||
|
#if NDEBUG
|
||||||
|
size_t nn = 30;
|
||||||
|
#else
|
||||||
|
size_t nn = 3;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (size_t n = 0; n < nn; n++)
|
||||||
|
test_external_pointer(r);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
83
src/test/perf/singlethread/singlethread.cc
Normal file
83
src/test/perf/singlethread/singlethread.cc
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#include <snmalloc.h>
|
||||||
|
#include <test/measuretime.h>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
using namespace snmalloc;
|
||||||
|
|
||||||
|
template<ZeroMem zero_mem>
|
||||||
|
void test_alloc_dealloc(size_t count, size_t size, bool write)
|
||||||
|
{
|
||||||
|
auto* alloc = ThreadAlloc::get();
|
||||||
|
|
||||||
|
DO_TIME(
|
||||||
|
"Count: " << std::setw(6) << count << ", Size: " << std::setw(6) << size
|
||||||
|
<< ", ZeroMem: " << (zero_mem == YesZero) << ", Write: " << write,
|
||||||
|
{
|
||||||
|
std::unordered_set<void*> set;
|
||||||
|
|
||||||
|
// alloc 1.5x objects
|
||||||
|
for (size_t i = 0; i < ((count * 3) / 2); i++)
|
||||||
|
{
|
||||||
|
void* p = alloc->alloc<zero_mem>(size);
|
||||||
|
assert(set.find(p) == set.end());
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
*(int*)p = 4;
|
||||||
|
|
||||||
|
set.insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// free 0.25x of the objects
|
||||||
|
for (size_t i = 0; i < (count / 4); i++)
|
||||||
|
{
|
||||||
|
auto it = set.begin();
|
||||||
|
void* p = *it;
|
||||||
|
alloc->dealloc(p, size);
|
||||||
|
set.erase(it);
|
||||||
|
assert(set.find(p) == set.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// alloc 1x objects
|
||||||
|
for (size_t i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
void* p = alloc->alloc<zero_mem>(size);
|
||||||
|
assert(set.find(p) == set.end());
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
*(int*)p = 4;
|
||||||
|
|
||||||
|
set.insert(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// free everything
|
||||||
|
while (!set.empty())
|
||||||
|
{
|
||||||
|
auto it = set.begin();
|
||||||
|
alloc->dealloc(*it, size);
|
||||||
|
set.erase(it);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
current_alloc_pool()->debug_check_empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int, char**)
|
||||||
|
{
|
||||||
|
for (size_t size = 16; size <= 128; size <<= 1)
|
||||||
|
{
|
||||||
|
test_alloc_dealloc<NoZero>(1 << 15, size, false);
|
||||||
|
test_alloc_dealloc<NoZero>(1 << 15, size, true);
|
||||||
|
test_alloc_dealloc<YesZero>(1 << 15, size, false);
|
||||||
|
test_alloc_dealloc<YesZero>(1 << 15, size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t size = 1 << 12; size <= 1 << 17; size <<= 1)
|
||||||
|
{
|
||||||
|
test_alloc_dealloc<NoZero>(1 << 10, size, false);
|
||||||
|
test_alloc_dealloc<NoZero>(1 << 10, size, true);
|
||||||
|
test_alloc_dealloc<YesZero>(1 << 10, size, false);
|
||||||
|
test_alloc_dealloc<YesZero>(1 << 10, size, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
42
src/test/usage.h
Normal file
42
src/test/usage.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# define WIN32_LEAN_AND_MEAN
|
||||||
|
# define NOMINMAX
|
||||||
|
# include <windows.h>
|
||||||
|
// Needs to be included after windows.h
|
||||||
|
# include <psapi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace usage
|
||||||
|
{
|
||||||
|
void print_memory()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32)
|
||||||
|
PROCESS_MEMORY_COUNTERS_EX pmc;
|
||||||
|
|
||||||
|
if (!GetProcessMemoryInfo(
|
||||||
|
GetCurrentProcess(), (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::cout << "Memory info:" << std::endl
|
||||||
|
<< "\tPageFaultCount: " << pmc.PageFaultCount << std::endl
|
||||||
|
<< "\tPeakWorkingSetSize: " << pmc.PeakWorkingSetSize << std::endl
|
||||||
|
<< "\tWorkingSetSize: " << pmc.WorkingSetSize << std::endl
|
||||||
|
<< "\tQuotaPeakPagedPoolUsage: " << pmc.QuotaPeakPagedPoolUsage
|
||||||
|
<< std::endl
|
||||||
|
<< "\tQuotaPagedPoolUsage: " << pmc.QuotaPagedPoolUsage
|
||||||
|
<< std::endl
|
||||||
|
<< "\tQuotaPeakNonPagedPoolUsage: "
|
||||||
|
<< pmc.QuotaPeakNonPagedPoolUsage << std::endl
|
||||||
|
<< "\tQuotaNonPagedPoolUsage: " << pmc.QuotaNonPagedPoolUsage
|
||||||
|
<< std::endl
|
||||||
|
<< "\tPagefileUsage: " << pmc.PagefileUsage << std::endl
|
||||||
|
<< "\tPeakPagefileUsage: " << pmc.PeakPagefileUsage << std::endl
|
||||||
|
<< "\tPrivateUsage: " << pmc.PrivateUsage << std::endl;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
71
src/test/xoroshiro.h
Normal file
71
src/test/xoroshiro.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace xoroshiro
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
template<typename STATE, typename RESULT, STATE A, STATE B, STATE C>
|
||||||
|
class XorOshiro
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
static constexpr unsigned STATE_BITS = 8 * sizeof(STATE);
|
||||||
|
static constexpr unsigned RESULT_BITS = 8 * sizeof(RESULT);
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
STATE_BITS >= RESULT_BITS,
|
||||||
|
"STATE must have at least as many bits as RESULT");
|
||||||
|
|
||||||
|
STATE x;
|
||||||
|
STATE y;
|
||||||
|
|
||||||
|
static inline STATE rotl(STATE x, STATE k)
|
||||||
|
{
|
||||||
|
return (x << k) | (x >> (STATE_BITS - k));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
XorOshiro(STATE x_ = 5489, STATE y_ = 0) : x(x_), y(y_)
|
||||||
|
{
|
||||||
|
// If both zero, then this does not work
|
||||||
|
if (x_ == 0 && y_ == 0)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_state(STATE x_, STATE y_ = 0)
|
||||||
|
{
|
||||||
|
// If both zero, then this does not work
|
||||||
|
if (x_ == 0 && y_ == 0)
|
||||||
|
abort();
|
||||||
|
|
||||||
|
x = x_;
|
||||||
|
y = y_;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
RESULT next()
|
||||||
|
{
|
||||||
|
STATE r = x + y;
|
||||||
|
y ^= x;
|
||||||
|
x = rotl(x, A) ^ y ^ (y << B);
|
||||||
|
y = rotl(y, C);
|
||||||
|
// If both zero, then this does not work
|
||||||
|
if (x == 0 && y == 0)
|
||||||
|
abort();
|
||||||
|
return r >> (STATE_BITS - RESULT_BITS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
using p128r64 = detail::XorOshiro<uint64_t, uint64_t, 55, 14, 36>;
|
||||||
|
using p128r32 = detail::XorOshiro<uint64_t, uint32_t, 55, 14, 36>;
|
||||||
|
using p64r32 = detail::XorOshiro<uint32_t, uint32_t, 27, 7, 20>;
|
||||||
|
using p64r16 = detail::XorOshiro<uint32_t, uint16_t, 27, 7, 20>;
|
||||||
|
using p32r16 = detail::XorOshiro<uint16_t, uint16_t, 13, 5, 10>;
|
||||||
|
using p32r8 = detail::XorOshiro<uint16_t, uint8_t, 13, 5, 10>;
|
||||||
|
using p16r8 = detail::XorOshiro<uint8_t, uint8_t, 4, 7, 3>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user