diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 00000000..895e0bf3 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "docfx": { + "version": "2.76.0", + "commands": [ + "docfx" + ], + "rollForward": false + }, + "docfxtocgenerator": { + "version": "1.19.0", + "commands": [ + "DocFxTocGenerator" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 32e510c8..54d97477 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,9 @@ root = true end_of_line = lf indent_style = space +[*.yml] +indent_size = 2 + [*.{csproj,ilproj,props,targets}] indent_size = 2 diff --git a/.github/.github.csproj b/.github/.github.csproj new file mode 100644 index 00000000..4d7b3159 --- /dev/null +++ b/.github/.github.csproj @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.github/gen-test-matrix.ps1 b/.github/gen-test-matrix.ps1 new file mode 100644 index 00000000..5509425b --- /dev/null +++ b/.github/gen-test-matrix.ps1 @@ -0,0 +1,216 @@ +param ( + [string[]] $MatrixOutName, + [string] $GithubOutput +) + +$ErrorActionPreference = "Stop"; + +$operatingSystems = @( + [pscustomobject]@{ + name = "Windows"; + runner = "windows-latest"; + ridname = "win"; + arch = @("x86","x64"); # while .NET Framework supports Arm64, GitHub doesn't provide Arm windows runners + runnerArch = 1; + hasFramework = $true; + monoArch = @("win32", "win64", "win_arm64"); + monoDll = "mono-2.0-bdwgc.dll"; + }, + [pscustomobject]@{ + name = "Linux"; + runner = "ubuntu-latest"; + ridname = "linux"; + arch = @("x64"); + runnerArch = 0; + hasMono = $true; + monoArch = @("linux64"); + monoDll = "limonobdwgc-2.0.so"; # TODO + }, + [pscustomobject]@{ + name = "MacOS 13"; + runner = "macos-13"; + ridname = "osx"; + arch = @("x64"); + runnerArch = 0; + hasMono = $true; + monoArch = @("macos_x64"); + monoDll = "limonobdwgc-2.0.dylib"; + }, + [pscustomobject]@{ + #enable = $false; + name = "MacOS 14 (M1)"; + runner = "macos-14"; + ridname = "osx"; + arch = @("x64"<#, "arm64"#>); # x64 comes from Rosetta, and we disable arm64 mode for now because we don't support it yet + runnerArch = 1; + hasMono = $true; + monoArch = @("macos_x64", "macos_arm64"); + monoDll = "limonobdwgc-2.0.dylib"; + } +); + +$dotnetVersions = @( + [pscustomobject]@{ + name = ".NET Framework 4.x"; + id = 'fx'; + tfm = "net462"; + rids = @("win-x86","win-x64","win-arm64"); + isFramework = $true; + }, + [pscustomobject]@{ + name = ".NET Core 2.1"; + sdk = "2.1"; + tfm = "netcoreapp2.1"; + rids = @("win-x86","win-x64","linux-x64","osx-x64"); + needsRestore = $true; + }, + [pscustomobject]@{ + name = ".NET Core 3.0"; + sdk = "3.0"; + tfm = "netcoreapp3.0"; + rids = @("win-x86","win-x64","linux-x64","osx-x64"); + }, + [pscustomobject]@{ + name = ".NET Core 3.1"; + sdk = "3.1"; + tfm = "netcoreapp3.1"; + rids = @("win-x86","win-x64","linux-x64","osx-x64"); + }, + [pscustomobject]@{ + name = ".NET 5.0"; + sdk = "5.0"; + tfm = "net5.0"; + rids = @("win-x86","win-x64","linux-x64","osx-x64"); + }, + [pscustomobject]@{ + name = ".NET 6.0"; + sdk = "6.0"; + tfm = "net6.0"; + rids = @("win-x86","win-x64","win-arm64","linux-x64","linux-arm","linux-arm64","osx-x64","osx-arm64"); + pgo = $true; + }, + [pscustomobject]@{ + name = ".NET 7.0"; + sdk = "7.0"; + tfm = "net7.0"; + rids = @("win-x86","win-x64","win-arm64","linux-x64","linux-arm","linux-arm64","osx-x64","osx-arm64"); + pgo = $true; + }, + [pscustomobject]@{ + name = ".NET 8.0"; + sdk = "8.0"; + tfm = "net8.0"; + rids = @("win-x86","win-x64","win-arm64","linux-x64","linux-arm","linux-arm64","osx-x64","osx-arm64"); + pgo = $true; + } +); + +$monoTfm = "net462"; + +$monoVersions = @( + <# + [pscustomobject]@{ + name = "Unity Mono 6000.0.2"; + unityVersion = "6000.0.2"; + monoName = "MonoBleedingEdge"; + } + #> +); + +$jobs = @(); + +foreach ($os in $operatingSystems) +{ + if ($os.enable -eq $false) { continue; } + $outos = $os | Select-Object -ExcludeProperty arch,ridname,hasFramework,hasMono,monoArch,monoDll,runnerArch + + if ($os.hasMono -and $os.runnerArch -lt $os.arch.Length) + { + # this OS has a system mono, emit a job for that + $jobs += @( + [pscustomobject]@{ + title = "System Mono on $($os.name)"; + os = $outos; + dotnet = [pscustomobject]@{ + name = "Mono"; + id = "sysmono"; + needsRestore = $true; # Monos always need restore + isMono = $true; + systemMono = $true; + tfm = $monoTfm; + }; + arch = $os.arch[$os.runnerArch]; + } + ); + } + + foreach ($arch in $os.arch) + { + $rid = $os.ridname + "-" + $arch; + + foreach ($dotnet in $dotnetVersions) + { + if ($dotnet.enable -eq $false) { continue; } + + if ($dotnet.isFramework -and -not $os.hasFramework) + { + # we're looking at .NET Framework, but this OS doesn't support it + continue; + } + + if (-not $dotnet.rids -contains $rid) + { + # the current OS/arch/runtime triple is not supported by .NET, skip + continue; + } + + $outdotnet = $dotnet | Select-Object -ExcludeProperty rids + + $title = "$($dotnet.name) $arch on $($os.name)" + if ($dotnet.pgo) + { + # this runtime supports pgo; generate 2 jobs; one with it enabled, one without + $jobs += @( + [pscustomobject]@{ + title = $title + " (PGO Off)"; + os = $outos; + dotnet = $outdotnet; + arch = $arch; + usePgo = $false; + }, + [pscustomobject]@{ + title = $title + " (PGO On)"; + os = $outos; + dotnet = $outdotnet; + arch = $arch; + usePgo = $true; + } + ); + } + else + { + # this is a normal job; only add one + $jobs += @( + [pscustomobject]@{ + title = $title; + os = $outos; + dotnet = $outdotnet; + arch = $arch; + } + ); + } + } + + # TODO: non-system mono + } +} + +# TODO: support multiple batches +if ($jobs.Length -gt 256) +{ + Write-Error "Generated more than 256 jobs; actions will fail!"; +} + +$matrixObj = [pscustomobject]@{include = $jobs;}; +$matrixStr = ConvertTo-Json -Compress -Depth 5 $matrixObj; +echo "$($MatrixOutName[0])=$matrixStr" >> $GithubOutput; \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..11bc83c8 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,123 @@ +name: Build + +on: + workflow_call: + inputs: + os: + required: true + type: string + osname: + required: true + type: string + version: + required: true + type: string + no-suffix: + type: boolean + default: false + upload-packages: + type: boolean + default: false + upload-tests: + type: boolean + default: false + +defaults: + run: + shell: pwsh + +env: + DOTNET_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + NUGET_PACKAGES: ${{github.workspace}}/artifacts/pkg + +jobs: + build: + runs-on: ${{ inputs.os }} + name: Build + env: + LOG_FILE_NAME: testresults.${{ inputs.os }}.auxtests.trx + VersionSuffix: ${{ !inputs.no-suffix && format('daily.{0}', inputs.version) }} + DoNotAddSuffix: ${{ inputs.no-suffix && '1' }} + steps: + - name: Configure git + run: | + git config --global core.autocrlf input + + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + submodules: recursive + + # TODO: maybe we can eventually use package locks for package caching? + + - name: Install .NET SDK + uses: nike4613/install-dotnet@54b402247e474b39b84891b9093d8025892c8b47 + with: + global-json: global.json + + # NOTE: manual package caching + - name: Cache restored NuGet packages + uses: actions/cache@v4 + with: + path: ${{ env.NUGET_PACKAGES }} + key: ${{ runner.os }}-nuget-v1-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets', 'nuget.config', 'global.json') }} + restore-keys: ${{ runner.os }}-nuget-v1- + + - name: Restore + run: dotnet restore -bl:restore.binlog -noAutoRsp + + - name: Build + run: dotnet build --no-restore -c Release -p:ContinuousIntegrationBuild=true -bl:build.binlog -clp:NoSummary -noAutoRsp + + - name: Pack + run: dotnet pack --no-restore -c Release -p:ContinuousIntegrationBuild=true -bl:pack.binlog -clp:NoSummary -noAutoRsp + + # TODO: it might be worth trying to do a "smoketest" test run with the installed tfm to broadly make sure that all builds + # work. We may also want to do a hash check on the packages, though I have no idea how reliable that would be. + + # TODO: If/when we add other test projects aside from MonoMod.UnitTest, we should run tests here + #- name: Run auxiliary tests + # run: dotnet test --no-build -c Release -l:"trx;LogFileName=$($env:LOG_FILE_NAME)" --filter "FullyQualifiedName!~MonoMod.UnitTest" + # + #- name: Upload test results + # uses: actions/upload-artifact@v4 + # if: ${{ always() }} + # with: + # name: test-results aux ${{ runner.os }} + # retention-days: 1 + # path: 'TestResults/*.trx' + # if-no-files-found: ignore + + - name: Upload binlogs + uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: binlogs-${{ runner.os }} + path: '*.binlog' + retention-days: 7 + + - name: Archive packages + uses: actions/upload-artifact@v4 + if: ${{ inputs.upload-packages }} + with: + name: packages + path: artifacts/package/release/*.nupkg + + - name: Archive packages + uses: actions/upload-artifact@v4 + if: ${{ !inputs.upload-packages }} + with: + name: packages ${{ runner.os }} + path: artifacts/package/release/*.nupkg + retention-days: 7 + + - name: Upload test assets + uses: actions/upload-artifact@v4 + if: ${{ inputs.upload-tests }} + with: + name: test-assets + retention-days: 1 + path: | + artifacts/bin/MonoMod.UnitTest/*/**/* \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..bd12fe91 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,94 @@ +name: Build and Test +on: + push: + pull_request: + +defaults: + run: + shell: pwsh + +env: + DOTNET_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + NUGET_PACKAGES: ${{github.workspace}}/artifacts/pkg + +# We'll have a job for building (that runs on x64 machines only, one for each OS to make sure it actually builds) +# Then, we'll take the result from one of those (probaly Linux) and distribute build artifacts to testers to run +# a load of tests. This will (eventually) include ARM runners, where possible. +jobs: + event_file: + # This job uploads an event file so that our test aggregator and recorder can understand this event + name: "Event File" + runs-on: ubuntu-latest + steps: + - name: Upload + uses: actions/upload-artifact@v4 + with: + name: test-event-file + path: ${{ github.event_path }} + retention-days: 1 + + compute-version: + name: Compute Version + runs-on: ubuntu-latest + outputs: + ver: ${{ steps.computever.outputs.ver }} + steps: + - id: computever + run: echo "ver=$(Get-Date -Format y.M.d).${{ github.run_number }}.${{ github.run_attempt }}" >> $env:GITHUB_OUTPUT + + compute-test-matrix: + name: Compute Test Matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.compute-matrix.outputs.matrix }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: false + submodules: false + - name: Compute test matrix + id: compute-matrix + run: ./.github/gen-test-matrix.ps1 -MatrixOutName matrix -GithubOutput $env:GITHUB_OUTPUT + + build-testassets: + needs: compute-version + name: 'Build #${{ needs.compute-version.outputs.ver }} (Linux)' + uses: ./.github/workflows/build.yml + with: + os: ubuntu-latest + osname: Linux + version: ${{ needs.compute-version.outputs.ver }} + upload-packages: true + upload-tests: true + + build: + needs: compute-version + strategy: + matrix: + os: [windows-latest, macos-13] + include: + - os: windows-latest + name: Windows + - os: macos-13 + name: MacOS + + name: 'Build #${{ needs.compute-version.outputs.ver }} (${{ matrix.name }})' + uses: ./.github/workflows/build.yml + with: + os: ${{ matrix.os }} + osname: ${{ matrix.name }} + version: ${{ needs.compute-version.outputs.ver }} + upload-packages: false + upload-tests: false + + test: + needs: [compute-test-matrix, build-testassets] + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.compute-test-matrix.outputs.matrix) }} + uses: ./.github/workflows/test.yml + name: Test ${{ matrix.title }} + with: + matrix: ${{ toJSON(matrix) }} \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000..8a344835 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,86 @@ +name: Docs +on: + push: + branches: + - reorganize + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + actions: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +defaults: + run: + shell: pwsh + +env: + DOTNET_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + NUGET_PACKAGES: ${{github.workspace}}/artifacts/pkg + +jobs: + publish-docs: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Configure git + run: | + git config --global core.autocrlf input + + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + submodules: recursive + + - name: Install .NET SDK + uses: nike4613/install-dotnet@54b402247e474b39b84891b9093d8025892c8b47 + with: + global-json: global.json + + # NOTE: manual package caching + - name: Cache restored NuGet packages + uses: actions/cache@v4 + with: + path: ${{ env.NUGET_PACKAGES }} + key: ${{ runner.os }}-nuget-v1-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets', 'nuget.config', 'global.json') }} + restore-keys: ${{ runner.os }}-nuget-v1- + + - name: Restore + run: dotnet restore -bl:restore.binlog -noAutoRsp + + - name: Restore .NET tools + run: dotnet tool restore + + - name: Build all projects + run: dotnet build -c Release -noAutoRsp -bl:build.binlog -p:RunAnalyzers=false # don't run any analyzers to speed up this build + + - name: Build DocFX project + run: dotnet docfx docfx/docfx.json + + - name: Upload binlogs + uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: binlogs + path: '*.binlog' + retention-days: 7 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + # Upload entire repository + path: 'docfx/_site' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..6eabdf53 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,118 @@ +name: Test + +on: + workflow_call: + inputs: + matrix: + required: true + type: string + +defaults: + run: + shell: pwsh + +env: + DOTNET_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + NUGET_PACKAGES: ${{github.workspace}}/artifacts/pkg + XunitVersion: "2.4.2" + +jobs: + test: + name: Test + runs-on: ${{ fromJSON(inputs.matrix).os.runner }} + env: + LOG_FILE_NAME: testresults.${{ fromJSON(inputs.matrix).os.runner }}.${{ fromJSON(inputs.matrix).dotnet.id != '' && fromJSON(inputs.matrix).dotnet.id || fromJSON(inputs.matrix).dotnet.sdk }}.${{ fromJSON(inputs.matrix).arch }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + if: ${{ fromJSON(inputs.matrix).dotnet.needsRestore }} + with: + lfs: true + submodules: recursive + + # Note: All of the SDKs we install have to be for the target architecture. Otherwise, we get issues when the default != the target. + - name: Install global SDK + if: ${{ ! fromJSON(inputs.matrix).dotnet.needsRestore }} + uses: nike4613/install-dotnet@54b402247e474b39b84891b9093d8025892c8b47 + with: + architecture: ${{ fromJSON(inputs.matrix).arch }} + version: "8.0" + + - name: Cache restored NuGet packages + uses: actions/cache@v4 + if: ${{ fromJSON(inputs.matrix).dotnet.needsRestore }} + with: + path: ${{ env.NUGET_PACKAGES }} + key: ${{ runner.os }}-nuget-v1-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets', 'nuget.config', 'global.json') }} + restore-keys: ${{ runner.os }}-nuget-v1- + + - name: Install restore SDK + if: ${{ fromJSON(inputs.matrix).dotnet.needsRestore }} + uses: nike4613/install-dotnet@54b402247e474b39b84891b9093d8025892c8b47 + with: + architecture: ${{ fromJSON(inputs.matrix).arch }} + global-json: global.json + + - name: Restore packages + if: ${{ fromJSON(inputs.matrix).dotnet.needsRestore }} + run: dotnet restore -noAutoRsp + + - name: Download test assets + uses: actions/download-artifact@v4 + with: + name: test-assets + + - name: Install test target runtime + if: ${{ fromJSON(inputs.matrix).dotnet.sdk != '' }} + uses: nike4613/install-dotnet@54b402247e474b39b84891b9093d8025892c8b47 + with: + version: ${{ fromJSON(inputs.matrix).dotnet.sdk }} + architecture: ${{ fromJSON(inputs.matrix).arch }} + runtime: dotnet + + - name: Print SDK info + run: dotnet --info + + - name: Fix runtimeconfig.json probing paths + if: ${{ fromJSON(inputs.matrix).dotnet.needsRestore }} + run: | + ConvertTo-Json @{runtimeOptions=@{additionalProbingPaths=@($env:NUGET_PACKAGES)}} > release_${{ fromJSON(inputs.matrix).dotnet.tfm }}/MonoMod.UnitTest.runtimeconfig.dev.json + + - name: Run tests + if: ${{ ! fromJSON(inputs.matrix).dotnet.isMono && ! fromJSON(inputs.matrix).dotnet.pgo }} + run: | + dotnet test -f ${{ fromJSON(inputs.matrix).dotnet.tfm }} -a ${{ fromJSON(inputs.matrix).arch }} ` + -l:"trx;LogFileName=$($env:LOG_FILE_NAME).trx" release_${{ fromJSON(inputs.matrix).dotnet.tfm }}/MonoMod.UnitTest.dll + + - name: Run tests (PGO) + if: ${{ fromJSON(inputs.matrix).dotnet.pgo }} + env: + DOTNET_ReadyToRun: ${{ !fromJSON(inputs.matrix).usePgo && 1 || 0 }} + DOTNET_TC_QuicJitForLoops: ${{ fromJSON(inputs.matrix).usePgo && 1 || 0 }} + DOTNET_TieredPGO: ${{ fromJSON(inputs.matrix).usePgo && 1 || 0 }} + run: | + dotnet test -f ${{ fromJSON(inputs.matrix).dotnet.tfm }} -a ${{ fromJSON(inputs.matrix).arch }} ` + -l:"trx;LogFileName=$($env:LOG_FILE_NAME).trx" release_${{ fromJSON(inputs.matrix).dotnet.tfm }}/MonoMod.UnitTest.dll + + - name: Run tests (Mono) + if: ${{ fromJSON(inputs.matrix).dotnet.isMono && fromJSON(inputs.matrix).dotnet.systemMono }} + env: + TFM: ${{ fromJSON(inputs.matrix).dotnet.tfm }} + run: | + mono "$($env:NUGET_PACKAGES)/xunit.runner.console/$($env:XunitVersion)/tools/$($env:TFM)/xunit.console.exe" ` + "release_$($env:TFM)/MonoMod.UnitTest.dll" -junit "$($env:LOG_FILE_NAME).xml" + + # TODO: Non-system Mono + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: test-results ${{ fromJSON(inputs.matrix).title }} + retention-days: 1 + path: | + TestResults/*.trx + *.xml + diaglog.* diff --git a/.github/workflows/test_results.yml b/.github/workflows/test_results.yml new file mode 100644 index 00000000..c19958db --- /dev/null +++ b/.github/workflows/test_results.yml @@ -0,0 +1,63 @@ +name: Publish Test Results + +on: + workflow_run: + workflows: + - Build and Test + types: [completed] +permissions: {} + +jobs: + publish-test-results: + if: github.event.workflow_run.conclusion != 'skipped' && github.event.workflow_run.conclusion != 'cancelled' + runs-on: ubuntu-latest + name: Publish test results + + permissions: + checks: write + pull-requests: write + contents: read + issues: read + actions: read + + steps: + - name: Download test results + uses: actions/download-artifact@v4 + with: + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + name: test-event-file + merge-multiple: false + + - name: Download test results + uses: actions/download-artifact@v4 + with: + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ github.token }} + pattern: test-results * + merge-multiple: false + + #- name: Publish Test Results + # uses: EnricoMi/publish-unit-test-result-action@v2 + # with: + # commit: ${{ github.event.workflow_run.head_sha }} + # event_file: event.json + # event_name: ${{ github.event.workflow_run.event }} + # + # files: '**/*.trx' + # comment_mode: ${{ (github.event.workflow_run.event == 'pull_request' || github.event_name == 'pull_request') && 'failures' || 'off' }} + # report_individual_runs: true + # compare_to_earlier_commit: false + + - name: Publish Test Results + uses: nike4613/actions-test-results@v2 + with: + commit: ${{ github.event.workflow_run.head_sha }} + event_file: event.json + event_name: ${{ github.event.workflow_run.event }} + + files: | + **/*.trx + **/*.xml + comment_mode: failures + comment_on_commit: true diff --git a/.gitignore b/.gitignore index e565920f..8890c436 100644 --- a/.gitignore +++ b/.gitignore @@ -177,7 +177,6 @@ ClientBin/ *.publishsettings node_modules/ orleans.codegen.cs -docfx/ # RIA/Silverlight projects Generated_Code/ diff --git a/MonoMod.sln b/MonoMod.sln index 244d1c33..d7b48365 100644 --- a/MonoMod.sln +++ b/MonoMod.sln @@ -23,11 +23,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .gitignore = .gitignore - azure-pipelines-coretest.yml = azure-pipelines-coretest.yml - azure-pipelines-docfx.ps1 = azure-pipelines-docfx.ps1 - azure-pipelines-postbuild.yml = azure-pipelines-postbuild.yml - azure-pipelines-prepushcommon.ps1 = azure-pipelines-prepushcommon.ps1 - azure-pipelines.yml = azure-pipelines.yml Directory.Build.rsp = Directory.Build.rsp global.json = global.json icon.png = icon.png @@ -52,6 +47,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iced", "external\iced.cspro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoMod.Backports.Tasks", "src\MonoMod.Backports.Tasks\MonoMod.Backports.Tasks.csproj", "{ED959596-2FAE-4B3D-8DF8-8F0058E8068D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = ".github", ".github\.github.csproj", "{179EC228-CED4-429E-934F-422C96273F74}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{4A65448D-466F-4E87-9797-41F43787EFF3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docfx", "docfx\docfx.csproj", "{0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -188,6 +189,30 @@ Global {ED959596-2FAE-4B3D-8DF8-8F0058E8068D}.Release|Any CPU.Build.0 = Release|Any CPU {ED959596-2FAE-4B3D-8DF8-8F0058E8068D}.ReleaseTrace|Any CPU.ActiveCfg = ReleaseTrace|Any CPU {ED959596-2FAE-4B3D-8DF8-8F0058E8068D}.ReleaseTrace|Any CPU.Build.0 = ReleaseTrace|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.DebugTrace|Any CPU.ActiveCfg = DebugTrace|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.DebugTrace|Any CPU.Build.0 = DebugTrace|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.Release|Any CPU.Build.0 = Release|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.ReleaseTrace|Any CPU.ActiveCfg = ReleaseTrace|Any CPU + {179EC228-CED4-429E-934F-422C96273F74}.ReleaseTrace|Any CPU.Build.0 = ReleaseTrace|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.DebugTrace|Any CPU.ActiveCfg = DebugTrace|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.DebugTrace|Any CPU.Build.0 = DebugTrace|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.Release|Any CPU.Build.0 = Release|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.ReleaseTrace|Any CPU.ActiveCfg = ReleaseTrace|Any CPU + {4A65448D-466F-4E87-9797-41F43787EFF3}.ReleaseTrace|Any CPU.Build.0 = ReleaseTrace|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.DebugTrace|Any CPU.ActiveCfg = DebugTrace|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.DebugTrace|Any CPU.Build.0 = DebugTrace|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.ReleaseTrace|Any CPU.ActiveCfg = ReleaseTrace|Any CPU + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698}.ReleaseTrace|Any CPU.Build.0 = ReleaseTrace|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +220,9 @@ Global GlobalSection(NestedProjects) = preSolution {7B682BC4-241C-4B74-8EDC-2D7677A79337} = {BCABFBF3-BB48-4C21-9A52-E140015570A9} {119A01EE-2238-423C-BE5B-48C68D7D14A7} = {69977055-8E22-4EB0-850D-6EE7D592BC04} + {179EC228-CED4-429E-934F-422C96273F74} = {BCABFBF3-BB48-4C21-9A52-E140015570A9} + {4A65448D-466F-4E87-9797-41F43787EFF3} = {BCABFBF3-BB48-4C21-9A52-E140015570A9} + {0BE9C4EB-1595-49E7-A2AA-09CA7E74C698} = {BCABFBF3-BB48-4C21-9A52-E140015570A9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E10A3D07-6D9A-4898-B95F-268636312A67} diff --git a/README.md b/README.md index a2f96376..56c8264b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ General purpose .NET assembly modding "basework", powered by [cecil](https://git *MIT-licensed.* -[![Build status](https://img.shields.io/azure-devops/build/MonoMod/MonoMod/1.svg?style=flat-square)](https://dev.azure.com/MonoMod/MonoMod/_build/latest?definitionId=1) ![Deployment status](https://img.shields.io/azure-devops/release/MonoMod/572c97eb-dbaa-4a55-90e5-1d05431535bd/1/1.svg?style=flat-square) +[![Build and Test](https://github.com/MonoMod/MonoMod/actions/workflows/ci.yml/badge.svg)](https://github.com/MonoMod/MonoMod/actions/workflows/ci.yml) | GitHub: All | NuGet: Patcher | NuGet: Utils | NuGet: RuntimeDetour | NuGet: HookGen | |--|--|--|--|--| @@ -16,24 +16,7 @@ General purpose .NET assembly modding "basework", powered by [cecil](https://git ## Sections - [Introduction](#introduction) -- [Using MonoMod](#using-monomod) -- [Using ModInterop (ext)](/README-ModInterop.md) -- [Using RuntimeDetour & HookGen (ext)](/README-RuntimeDetour.md) -- [FAQ](#faq) - -### Special thanks to my [patrons on Patreon](https://www.patreon.com/0x0ade): -- [Chad Yates](https://twitter.com/ChadCYates) -- [Sc2ad](https://github.com/sc2ad) -- Raegous -- Chaser6 -- [Harrison Clarke](https://twitter.com/hay_guise) -- [KyleTheScientist](https://www.twitch.tv/kylethescientist) -- [Renaud Bédard](https://twitter.com/renaudbedard) -- [leo60228](https://leo60228.space) -- [Rubydragon](https://www.twitch.tv/rubydrag0n) -- Holly Magala -- [Jimmy Londo (iamdadbod)](https://www.youtube.com/iamdadbod) -- [Artus Elias Meyer-Toms](https://twitter.com/artuselias) +- [Using RuntimeDetour](docs/RuntimeDetour/Usage.md) ---- @@ -45,7 +28,7 @@ Mods / mod loaders for the following games are already using it in one way or an - Celeste: [Everest](https://everestapi.github.io/) - Risk of Rain 2: [BepInExPack (BepInEx + MonoMod + R2API)](https://thunderstore.io/package/bbepis/BepInExPack/) - Enter the Gungeon: [Mod the Gungeon](https://modthegungeon.github.io/) -- Rain World: [RainDB via Partiality](http://www.raindb.net/) +- Rain World: [RainDB via BepInEx](http://www.raindb.net/) - Totally Accurate Battle Simulator: [TABS-Multiplayer](https://github.com/Ceiridge/TABS-Multiplayer) - Salt and Sanctuary: [Salt.Modding](https://github.com/seanpr96/Salt.Modding) - Nimbatus: [Nimbatus-Mods via Partiality](https://github.com/OmegaRogue/Nimbatus-Mods) @@ -75,6 +58,16 @@ It consists of the following **modular components**: --- -### Debugging mods that use MonoMod - -See [Debugging](docs/Debugging.md). \ No newline at end of file +### Special thanks to my [patrons on Patreon](https://www.patreon.com/0x0ade): +- [Chad Yates](https://twitter.com/ChadCYates) +- [Sc2ad](https://github.com/sc2ad) +- Raegous +- Chaser6 +- [Harrison Clarke](https://twitter.com/hay_guise) +- [KyleTheScientist](https://www.twitch.tv/kylethescientist) +- [Renaud Bédard](https://twitter.com/renaudbedard) +- [leo60228](https://leo60228.space) +- [Rubydragon](https://www.twitch.tv/rubydrag0n) +- Holly Magala +- [Jimmy Londo (iamdadbod)](https://www.youtube.com/iamdadbod) +- [Artus Elias Meyer-Toms](https://twitter.com/artuselias) diff --git a/azure-pipelines-coretest.yml b/azure-pipelines-coretest.yml deleted file mode 100644 index 18434389..00000000 --- a/azure-pipelines-coretest.yml +++ /dev/null @@ -1,90 +0,0 @@ -parameters: -- name: targetFramework - type: string - default: '' -- name: buildConfiguration - type: string - default: 'Release' -- name: arch - type: string - default: '' -- name: condition - type: string - default: true -- name: usePgo - type: string - default: false -- name: pgoTimeout - type: string - default: 5 - -steps: -- ${{ if eq(length('${{parameters.arch}}'), 0) }}: - - task: CmdLine@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: core: ${{parameters.targetFramework}}' - inputs: - script: | - dotnet test --no-build -c ${{parameters.buildConfiguration}} -f ${{parameters.targetFramework}} -l:"trx;LogFileName=testresults.core.${{parameters.targetFramework}}.trx" - - task: PublishTestResults@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: Publish: core: ${{parameters.targetFramework}}' - inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '**/testresults.core.${{parameters.targetFramework}}.trx' - testRunTitle: 'Tests @ $(jobArchName) core ${{parameters.targetFramework}}' - -- ${{ if ne(length('${{parameters.arch}}'), 0) }}: - - task: CmdLine@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: core: ${{parameters.targetFramework}}-${{parameters.arch}}' - inputs: - script: | - dotnet test --no-build -c ${{parameters.buildConfiguration}} -f ${{parameters.targetFramework}} --arch '${{parameters.arch}}' -l:"trx;LogFileName=testresults.core.${{parameters.targetFramework}}-${{parameters.arch}}.trx" - - task: PublishTestResults@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: Publish: core: ${{parameters.targetFramework}}-${{parameters.arch}}' - inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '**/testresults.core.${{parameters.targetFramework}}-${{parameters.arch}}.trx' - testRunTitle: 'Tests @ $(jobArchName) core ${{parameters.targetFramework}}-${{parameters.arch}}' - -- ${{ if and(eq('${{parameters.usePgo}}', 'true'), eq(length('${{parameters.arch}}'), 0)) }}: - - task: CmdLine@2 - condition: and(always(), ${{parameters.condition}}) - timeoutInMinutes: ${{parameters.pgoTimeout}} - displayName: 'Test: core: ${{parameters.targetFramework}} PGO' - env: - DOTNET_ReadyToRun: 0 - DOTNET_TC_QuickJitForLoops: 1 - DOTNET_TieredPGO: 1 - inputs: - script: | - dotnet test --no-build -c ${{parameters.buildConfiguration}} -f ${{parameters.targetFramework}} -l:"trx;LogFileName=testresults.core.${{parameters.targetFramework}}.trx" - - task: PublishTestResults@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: Publish: core: ${{parameters.targetFramework}}' - inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '**/testresults.core.${{parameters.targetFramework}}.pgo.trx' - testRunTitle: 'Tests @ $(jobArchName) core ${{parameters.targetFramework}} PGO' - -- ${{ if and(eq('${{parameters.usePgo}}', 'true'), ne(length('${{parameters.arch}}'), 0)) }}: - - task: CmdLine@2 - condition: and(always(), ${{parameters.condition}}) - timeoutInMinutes: ${{parameters.pgoTimeout}} - displayName: 'Test: core: ${{parameters.targetFramework}}-${{parameters.arch}} PGO' - env: - DOTNET_ReadyToRun: 0 - DOTNET_TC_QuickJitForLoops: 1 - DOTNET_TieredPGO: 1 - inputs: - script: | - dotnet test --no-build -c ${{parameters.buildConfiguration}} -f ${{parameters.targetFramework}} --arch '${{parameters.arch}}' -l:"trx;LogFileName=testresults.core.${{parameters.targetFramework}}-${{parameters.arch}}.trx" - - task: PublishTestResults@2 - condition: and(always(), ${{parameters.condition}}) - displayName: 'Test: Publish: core: ${{parameters.targetFramework}}-${{parameters.arch}}' - inputs: - testResultsFormat: 'VSTest' - testResultsFiles: '**/testresults.core.${{parameters.targetFramework}}-${{parameters.arch}}.pgo.trx' - testRunTitle: 'Tests @ $(jobArchName) core ${{parameters.targetFramework}}-${{parameters.arch}} PGO' \ No newline at end of file diff --git a/azure-pipelines-docfx.ps1 b/azure-pipelines-docfx.ps1 deleted file mode 100644 index ca10e6af..00000000 --- a/azure-pipelines-docfx.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -$GitHubBotName=$args[0] -$GitHubBotEmail=$args[1] -$GitHubBotToken=$args[2] - -# PowerShell and git don't work well together. -$ErrorActionPreference="Continue" - -Write-Output "Setting up config" -$DocFXRepo="https://$GitHubBotToken@github.com/MonoMod/MonoMod.github.io.git" -git config --global user.name $GitHubBotName 2>&1 | ForEach-Object { "$_" } -git config --global user.email $GitHubBotEmail 2>&1 | ForEach-Object { "$_" } - -Write-Output "Setting up file hierarchy" -git clone --recursive --branch docfx $DocFXRepo docfx 2>&1 | ForEach-Object { "$_" } -Set-Location docfx -git clone --recursive --branch master $DocFXRepo _site 2>&1 | ForEach-Object { "$_" } - -Write-Output "Installing DocFX" -choco install docfx -y - -Write-Output "Running docfx build" -docfx metadata -docfx build - -Write-Output "Pushing updated _site to master branch on GitHub" -Set-Location _site -git add . 2>&1 | ForEach-Object { "$_" } -git commit --allow-empty -m "Rebuild (automatic commit via Azure Pipelines)" 2>&1 | ForEach-Object { "$_" } -git push 2>&1 | ForEach-Object { "$_" } - -Write-Output "Done" diff --git a/azure-pipelines-postbuild.yml b/azure-pipelines-postbuild.yml deleted file mode 100644 index 9b1d49ae..00000000 --- a/azure-pipelines-postbuild.yml +++ /dev/null @@ -1,32 +0,0 @@ -parameters: - targetFramework: '' - buildConfiguration: 'Release' - -steps: -- task: CopyFiles@2 - displayName: 'Artifacts: Pack: ${{parameters.targetFramework}}' - continueOnError: true - inputs: - sourceFolder: '$(Agent.BuildDirectory)' - # the leading **/ shouldn't be necessary, but it seems to be - contents: | - **/artifacts/bin/**/${{parameters.buildConfiguration}}/${{parameters.targetFramework}}/**/* - !**/MonoMod.FrameworkTests/**/* - !**/Monomod.Backports.Filter/**/* - !**/Monomod.ILHelpers.Patcher/**/* - !**/MonoMod.UnitTest/**/* - !**/xunit.* - !**/testhost* - !**/System.* - !**/Microsoft.* - targetFolder: '$(Build.ArtifactStagingDirectory)/${{parameters.targetFramework}}' - cleanTargetFolder: true - overWrite: true - flattenFolders: true -- task: PublishBuildArtifacts@1 - displayName: 'Artifacts: Publish: ${{parameters.targetFramework}}' - continueOnError: true - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)/${{parameters.targetFramework}}' - artifactName: '$(artifactPrefix)${{parameters.targetFramework}}$(artifactSuffix)' - publishLocation: 'Container' diff --git a/azure-pipelines-prepushcommon.ps1 b/azure-pipelines-prepushcommon.ps1 deleted file mode 100644 index a97dc263..00000000 --- a/azure-pipelines-prepushcommon.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -# MonoMod.Common doesn't exist. -#if ($(git rev-parse "@:MonoMod.Common") -eq $(git rev-parse "@~:MonoMod.Common")) { -# Write-Output "MonoMod.Common wasn't changed." -# for ($i = 0; $i -lt $args.Length; $i++) { -# Write-Output "Deleting $($args[$i])" -# Remove-Item -Path "$($args[$i])" -# } -#} else { -# Write-Output "MonoMod.Common was changed." -#} diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 8e2176a4..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,302 +0,0 @@ -strategy: - matrix: - windows: - jobArchName: 'Windows' - imageName: 'windows-latest' - artifactPrefix: '' - artifactSuffix: '' - testers: ';fx;core2.1;core3;core3.1;core5;core6;core7;core8;' - testx86: 'true' - generateDocFX: 'false' - pushNuGet: 'true' - linux: - jobArchName: 'Linux' - imageName: 'ubuntu-latest' - artifactPrefix: '~dbg.linux.' - artifactSuffix: '' - testers: ';mono;core2.1;core3;core3.1;core5;core6;core7;core8;' - testx86: 'false' - generateDocFX: 'false' - pushNuGet: 'false' - macos: - jobArchName: 'macOS' - imageName: 'macOS-latest' - artifactPrefix: '~dbg.macos.' - artifactSuffix: '' - testers: ';mono;core2.1;core3;core3.1;core5;core6;core7;core8;' - testx86: 'false' - generateDocFX: 'false' - pushNuGet: 'false' - maxParallel: 4 - -variables: - solution: '**/*.sln' - buildConfiguration: 'Release' - buildPlatform: 'Any CPU' - xunitVer: '2.4.2' - xunit: '{0}{1}/.nuget/packages/xunit.runner.console/$(xunitVer)/tools/{2}/xunit.console.{3} artifacts/bin/MonoMod.UnitTest/Release/{6}/MonoMod.UnitTest.dll -xml testresults.{4}.{6}.xml {5}' - -name: '$(Date:y.M.d).$(Rev:r)' - -pool: - vmImage: '$(imageName)' - -steps: -- checkout: self - clean: false - fetchDepth: 2 - lfs: false - submodules: recursive - -# Run pre-build steps. -- task: UseDotNet@2 - displayName: 'Install .NET Core SDK 2.1.x' - inputs: - packageType: sdk - version: '2.1.x' -- task: UseDotNet@2 - displayName: 'Install .NET Core SDK 3.0.x' - inputs: - packageType: sdk - version: '3.0.x' -- task: UseDotNet@2 - displayName: 'Install .NET Core SDK 3.1.x' - inputs: - packageType: sdk - version: '3.1.x' -- task: UseDotNet@2 - condition: contains(variables.testers, ';core5;') - displayName: 'Install .NET SDK 5.0.x' - inputs: - packageType: sdk - version: '5.0.x' -- task: UseDotNet@2 - condition: contains(variables.testers, ';core6;') - displayName: 'Install .NET SDK 6.0.x' - inputs: - packageType: sdk - version: '6.0.x' -- task: UseDotNet@2 - condition: contains(variables.testers, ';core7;') - displayName: 'Install .NET SDK 7.0.x' - inputs: - packageType: sdk - version: '7.0.x' -- task: UseDotNet@2 - condition: contains(variables.testers, ';core8;') - displayName: 'Install .NET SDK 8.0.x' - inputs: - packageType: sdk - version: '8.0.x' - includePreviewVersions: true -- task: NuGetToolInstaller@1 - displayName: 'Update NuGet' - inputs: - checkLatest: true -- task: DotNetCoreCLI@2 - displayName: 'dotnet: Restore' - inputs: - command: 'restore' - arguments: '-p:XunitVersion="$(xunitVer)"' - -# Build using core. Mono's msbuild started to cause too many issues. -- task: DotNetCoreCLI@2 - displayName: 'dotnet: Build' - inputs: - command: 'build' - arguments: '-c $(buildConfiguration) -p:XunitVersion="$(xunitVer)" -p:VersionSuffix="daily.$(Build.BuildNumber)" -p:ContinuousIntegrationBuild=true -tl:off' - -# Create and "publish" main artifacts. -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'net35' -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'net452' -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'netstandard2.0' -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'net5.0' -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'net6.0' -- template: 'azure-pipelines-postbuild.yml' - parameters: - targetFramework: 'net7.0' -# - template: 'azure-pipelines-postbuild.yml' -# parameters: -# targetFramework: 'net8.0' - -# Create and "publish" nupkg artifacts. -- task: DotNetCoreCLI@2 - displayName: 'Artifacts: Pack: nupkgs' - continueOnError: true - inputs: - #command: 'pack' - command: 'custom' # if you use command: pack, then you *have* to set packagesToPack, despite not providing it doing the right thing - custom: 'pack' - arguments: '-c $(buildConfiguration) -p:XunitVersion="$(xunitVer)" -p:VersionSuffix="daily.$(Build.BuildNumber)" -p:ContinuousIntegrationBuild=true' - nobuild: true - configuration: '$(buildConfiguration)' - versioningScheme: 'byBuildNumber' - #packagesToPack: 'MonoMod@(|.Common|.Utils|.RuntimeDetour|.RuntimeDetour.HookGen)/*.csproj' - packDirectory: '$(Build.ArtifactStagingDirectory)/nupkgs' -- task: PublishBuildArtifacts@1 - displayName: 'Artifacts: Publish: nupkgs' - continueOnError: true - inputs: - pathtoPublish: 'artifacts/packages/$(buildConfiguration)' - artifactName: '$(artifactPrefix)nupkgs$(artifactSuffix)' - publishLocation: 'Container' -- task: NuGetCommand@2 - condition: and(always(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.pushNuGet, 'true')) - displayName: 'Artifacts: Push: nupkgs' - inputs: - command: push - packagesToPush: 'artifacts/packages/$(buildConfiguration)/*.nupkg' - publishVstsFeed: '572c97eb-dbaa-4a55-90e5-1d05431535bd/72ad568d-c548-4599-8b0a-9ea52b45bbbd' -#- task: PowerShell@2 -# condition: and(always(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.pushNuGet, 'true')) -# displayName: 'Artifacts: Prepare: MonoMod.Common to nuget.org' -# continueOnError: true -# inputs: -# filePath: 'azure-pipelines-prepushcommon.ps1' -# arguments: '$(Build.ArtifactStagingDirectory)/nupkgs/MonoMod.Common.*.nupkg' -#- task: NuGetCommand@2 -# condition: and(always(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.pushNuGet, 'true')) -# displayName: 'Artifacts: Push: MonoMod.Common to nuget.org' -# inputs: -# command: 'push' -# packagesToPush: '$(Build.ArtifactStagingDirectory)/nupkgs/MonoMod.Common.*.nupkg' -# nuGetFeedType: 'external' -# publishFeedCredentials: 'NuGet MonoMod' - -# Run tests. -# I've tried to simplify this mess. It's impossible. --ade -- task: CmdLine@2 - condition: and(always(), contains(variables.testers, ';fx;')) - displayName: 'Test: fx: net452' - inputs: - script: ${{format(variables.xunit, '', '%userprofile%', 'net452', 'exe', 'fx', '', 'net46')}} -- task: PublishTestResults@2 - condition: and(always(), contains(variables.testers, ';fx;')) - displayName: 'Test: Publish: fx: net46' - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '**/testresults.fx.net46.xml' - testRunTitle: 'Tests @ $(jobArchName) fx net46' - -- task: CmdLine@2 - condition: and(always(), contains(variables.testers, ';mono;')) - displayName: 'Test: mono: net46' - inputs: - script: ${{format(variables.xunit, 'mono --debug ', '~', 'net452', 'exe', 'mono', '', 'net46')}} -- task: PublishTestResults@2 - condition: and(always(), contains(variables.testers, ';mono;')) - displayName: 'Test: Publish: mono: net46' - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '**/testresults.mono.net46.xml' - testRunTitle: 'Tests @ $(jobArchName) mono net46' - -- task: CmdLine@2 - condition: and(always(), contains(variables.testers, ';monoslow;')) - displayName: 'Test: monoslow: net46' - inputs: - script: ${{format(variables.xunit, 'mono --debug ', '~', 'net452', 'exe', 'monoslow', '-parallel none -appdomains denied -verbose', 'net46')}} -- task: PublishTestResults@2 - condition: and(always(), contains(variables.testers, ';monoslow;')) - displayName: 'Test: Publish: monoslow: net46' - inputs: - testResultsFormat: 'xUnit' - testResultsFiles: '**/testresults.monoslow.net46.xml' - testRunTitle: 'Tests @ $(jobArchName) monoslow net46' - -# All of the .NET Core tests run via azure-pipelines-coretest.yml, and have an optional 'arch' parameter to allow specific arch selection -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core2.1;')) - targetFramework: 'netcoreapp2.1' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core3;')) - targetFramework: 'netcoreapp3.0' - arch: 'x64' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core3;'), eq(variables.testx86, 'true')) - targetFramework: 'netcoreapp3.0' - arch: 'x86' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core3.1;')) - targetFramework: 'netcoreapp3.1' - arch: 'x64' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core3.1;'), eq(variables.testx86, 'true')) - targetFramework: 'netcoreapp3.1' - arch: 'x86' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core5;')) - targetFramework: 'net5.0' - arch: 'x64' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core5;'), eq(variables.testx86, 'true')) - targetFramework: 'net5.0' - arch: 'x86' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core6;')) - targetFramework: 'net6.0' - arch: 'x64' - usePgo: 'true' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core6;'), eq(variables.testx86, 'true')) - targetFramework: 'net6.0' - arch: 'x86' - usePgo: 'true' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core7;')) - targetFramework: 'net7.0' - arch: 'x64' - usePgo: 'true' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core7;'), eq(variables.testx86, 'true')) - targetFramework: 'net7.0' - arch: 'x86' - usePgo: 'true' - -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(true, contains(variables.testers, ';core8;')) - targetFramework: 'net8.0' - arch: 'x64' - usePgo: 'true' -- template: 'azure-pipelines-coretest.yml' - parameters: - condition: and(contains(variables.testers, ';core8;'), eq(variables.testx86, 'true')) - targetFramework: 'net8.0' - arch: 'x86' - usePgo: 'true' - -# Rebuild and publish the DocFX page. -- task: PowerShell@2 - condition: and(always(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.generateDocFX, 'true')) - displayName: 'DocFX' - continueOnError: true - inputs: - filePath: 'azure-pipelines-docfx.ps1' - arguments: '$(GitHubBotName) $(GitHubBotEmail) $(GitHubBotToken)' diff --git a/docfx/.gitignore b/docfx/.gitignore new file mode 100644 index 00000000..2781f6d5 --- /dev/null +++ b/docfx/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/docfx/CNAME b/docfx/CNAME new file mode 100644 index 00000000..0fa7e435 --- /dev/null +++ b/docfx/CNAME @@ -0,0 +1 @@ +monomod.dev diff --git a/docfx/api/.gitignore b/docfx/api/.gitignore new file mode 100644 index 00000000..f798527e --- /dev/null +++ b/docfx/api/.gitignore @@ -0,0 +1,5 @@ +############### +# temp file # +############### +*.yml +.manifest diff --git a/docfx/api/index.md b/docfx/api/index.md new file mode 100644 index 00000000..e69de29b diff --git a/docfx/articles/intro.md b/docfx/articles/intro.md new file mode 100644 index 00000000..901a16a4 --- /dev/null +++ b/docfx/articles/intro.md @@ -0,0 +1 @@ +# Add your introductions here! diff --git a/docfx/articles/toc.yml b/docfx/articles/toc.yml new file mode 100644 index 00000000..f64352ce --- /dev/null +++ b/docfx/articles/toc.yml @@ -0,0 +1,2 @@ +- name: Introduction + href: intro.md diff --git a/docfx/docfx.csproj b/docfx/docfx.csproj new file mode 100644 index 00000000..e41366b7 --- /dev/null +++ b/docfx/docfx.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/docfx/docfx.json b/docfx/docfx.json new file mode 100644 index 00000000..eb2cf141 --- /dev/null +++ b/docfx/docfx.json @@ -0,0 +1,91 @@ +{ + "metadata": [ + { + "src": [ + { + "src": "..", + "files": [ + "src/**/MonoMod*.csproj" + ], + "exclude": [ + "**/MonoMod.FrameworkTests.*", + "**/MonoMod.SourceGen.*.*", + "**/MonoMod.UnitTest.*", + "**/MonoMod.Backports.Tasks.*", + "**/MonoMod.Patcher.*", + "**/MonoMod.ILHelpers.Patcher.*", + "**/MonoMod.DebugIL.*", + "**/MonoMod.RuntimeDetour.HookGen.*" + ] + } + ], + "dest": "api", + "disableGitFeatures": false, + "disableDefaultFilter": false, + "shouldSkipMarkup": true, + "outputFormat": "mref", + "filter": "filterConfig.yml", + "categoryLayout": "nested", + "namespaceLayout": "flattened", + "memberLayout": "separatePages", + "enumSortOrder": "declaringOrder" + } + ], + "build": { + "content": [ + { + "files": [ + "api/**.yml", + "api/index.md" + ] + }, + { + "files": [ + "articles/**.md", + "articles/**/toc.yml", + "toc.yml", + "*.md" + ] + }, + { + "src": "../docs", + "dest": "docs", + "files": [ + "**.md", + "**/toc.yml" + ] + } + ], + "resource": [ + { + "files": [ + "images/**", + "CNAME" + ] + } + ], + "overwrite": [ + { + "files": [ + "apidoc/**.md" + ], + "exclude": [ + "obj/**", + "_site/**" + ] + } + ], + "dest": "_site", + "globalMetadataFiles": ["globalmeta.json"], + "fileMetadataFiles": [], + "template": [ + "default", "modern" + ], + "postProcessors": ["ExtractSearchIndex"], + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} \ No newline at end of file diff --git a/docfx/filterConfig.yml b/docfx/filterConfig.yml new file mode 100644 index 00000000..cad31780 --- /dev/null +++ b/docfx/filterConfig.yml @@ -0,0 +1,7 @@ +apiRules: +- exclude: + uidRegex: ^System.* + type: Namespace +- exclude: + uidRegex: ^Iced.* + type: Namespace \ No newline at end of file diff --git a/docfx/globalmeta.json b/docfx/globalmeta.json new file mode 100644 index 00000000..928b553e --- /dev/null +++ b/docfx/globalmeta.json @@ -0,0 +1,13 @@ +{ + "_appTitle": "MonoMod", + "_appName": "MonoMod", + "_appFooter": "Yet another C# modding swiss army knife.", + "_description": "A C# modding swiss army knife, powered by cecil.", + "author": "0x0ade and contributors", + "url": "https://monomod.github.io/", + "image": "https://monomod.github.io/favicon.png", + "_appLogoPath": "images/logo-small.png", + "_appFaviconPath": "images/favicon.png", + "_enableSearch": true, + "_enableNewTab": true +} \ No newline at end of file diff --git a/docfx/images/favicon.png b/docfx/images/favicon.png new file mode 100644 index 00000000..5aadb2ad Binary files /dev/null and b/docfx/images/favicon.png differ diff --git a/docfx/images/logo-small.png b/docfx/images/logo-small.png new file mode 100644 index 00000000..fedf971d Binary files /dev/null and b/docfx/images/logo-small.png differ diff --git a/docfx/index.md b/docfx/index.md new file mode 100644 index 00000000..53c77a07 --- /dev/null +++ b/docfx/index.md @@ -0,0 +1 @@ +[!INCLUDE [readme](../README.md)] \ No newline at end of file diff --git a/docfx/toc.yml b/docfx/toc.yml new file mode 100644 index 00000000..3bcca430 --- /dev/null +++ b/docfx/toc.yml @@ -0,0 +1,10 @@ +- name: Home + href: index.md +- name: API + href: api/ +- name: DevDocs + href: docs/ +- name: GitHub + href: https://github.com/MonoMod +- name: Discord + href: https://discord.gg/jm7GCZB \ No newline at end of file diff --git a/docs/Core/index.md b/docs/Core/index.md new file mode 100644 index 00000000..3b67f455 --- /dev/null +++ b/docs/Core/index.md @@ -0,0 +1,5 @@ +# MonoMod.Core + +- [Discussion about the CoreCLR JIT hook design](CoreCLRJitHooks.md) +- [`INativeExceptionHelper`](NativeExceptionHandler.md) +- [MonoMod.Core's Architecture](Architecture.md) diff --git a/docs/Core/toc.yml b/docs/Core/toc.yml new file mode 100644 index 00000000..543d8301 --- /dev/null +++ b/docs/Core/toc.yml @@ -0,0 +1,8 @@ +href: index.md +items: +- name: Discussion about the CoreCLR JIT hook design + href: CoreCLRJitHooks.md +- name: INativeExceptionHelper + href: NativeExceptionHandler.md +- name: MonoMod.Core's Architecture + href: Architecture.md diff --git a/docs/README.ILHelpers.md b/docs/README.ILHelpers.md index b2033350..cf52b15a 100644 --- a/docs/README.ILHelpers.md +++ b/docs/README.ILHelpers.md @@ -1,13 +1 @@ -# `MonoMod.ILHelpers` - -`MonoMod.ILHelpers` is a collection of helpers manually implemented in IL. - -Notably, this contains a backport of `System.Runtime.CompilerServices.Unsafe`, as it exists in .NET 6, to all older -runtimes. This means that any environment which *also* provides that class which is older than .NET 6 will require -an `extern alias` to be able to use properly. - -## Notable APIs - -- `System.Runtime.CompilerServices.Unsafe` -- `MonoMod.ILHelpers` -- \ No newline at end of file +# DO NOT REFERENCE THIS PACKAGE DIRECTLY! Reference MonoMod.Backports instead. \ No newline at end of file diff --git a/docs/RuntimeDetour.HookGen/index.md b/docs/RuntimeDetour.HookGen/index.md new file mode 100644 index 00000000..1a64eb10 --- /dev/null +++ b/docs/RuntimeDetour.HookGen/index.md @@ -0,0 +1,3 @@ +# MonoMod.RuntimeDetour.HookGen + +* [Using HookGen](Usage.md) diff --git a/docs/RuntimeDetour.HookGen/toc.yml b/docs/RuntimeDetour.HookGen/toc.yml new file mode 100644 index 00000000..f97f6084 --- /dev/null +++ b/docs/RuntimeDetour.HookGen/toc.yml @@ -0,0 +1,4 @@ +href: index.md +items: +- name: Using HookGen + href: Usage.md diff --git a/docs/RuntimeDetour/implementation/index.md b/docs/RuntimeDetour/implementation/index.md new file mode 100644 index 00000000..14c856b7 --- /dev/null +++ b/docs/RuntimeDetour/implementation/index.md @@ -0,0 +1,3 @@ +# Implementation + +* [ChainHotPatching](ChainHotPatching.md) diff --git a/docs/RuntimeDetour/implementation/toc.yml b/docs/RuntimeDetour/implementation/toc.yml new file mode 100644 index 00000000..4fb1fe92 --- /dev/null +++ b/docs/RuntimeDetour/implementation/toc.yml @@ -0,0 +1,4 @@ +href: index.md +items: +- name: ChainHotPatching + href: ChainHotPatching.md diff --git a/docs/RuntimeDetour/index.md b/docs/RuntimeDetour/index.md new file mode 100644 index 00000000..d21d5a3f --- /dev/null +++ b/docs/RuntimeDetour/index.md @@ -0,0 +1,4 @@ +# MonoMod.RuntimeDetour + +- [Implementation](implementation/index.md) +- [Using RuntimeDetour](Usage.md) diff --git a/docs/RuntimeDetour/toc.yml b/docs/RuntimeDetour/toc.yml new file mode 100644 index 00000000..e75c196c --- /dev/null +++ b/docs/RuntimeDetour/toc.yml @@ -0,0 +1,7 @@ +# This is an automatically generated file +href: index.md +items: +- name: Implementation + href: implementation/toc.yml +- name: Using RuntimeDetour + href: Usage.md diff --git a/docs/RuntimeIssueNotes.md b/docs/RuntimeIssueNotes.md new file mode 100644 index 00000000..0bdbb3f2 --- /dev/null +++ b/docs/RuntimeIssueNotes.md @@ -0,0 +1,128 @@ +# Notes on issues in various runtime versions + +Martin, this is wrong. + +## `sizeof` IL opcode does not work with generic parameters on old Mono + +The title says it all. `sizeof` works fine with all other type-specs, but with generic parameters specifically, +it always returns the system pointer size. + +The relevant code is in `metadata/metadata.c`, in `mono_type_size` (which `sizeof` correctly embeds as a constant): + +```c +int +mono_type_size (MonoType *t, int *align) +{ + // ... + + switch (t->type){ + // ... + case MONO_TYPE_VAR: + case MONO_TYPE_MVAR: + /* FIXME: Martin, this is wrong. */ + *align = __alignof__(gpointer); + return sizeof (gpointer); + // ... + } + + // ... +} +``` + +## `fixed` on strings in old Mono + +Some old versions of Mono have broken `conv.u` instruction handling. + +The following code will crash those old versions with an assert in the JIT's local propagation routine: + +```csharp +fixed (char* pStr = "some string") +{ + // ... +} +``` + +This is because the sequence that Roslyn emits for `fixed` over a string is this: + +```il + .locals ( + string pinned stringLocalMarkedPinned, + char* ptrLocal + ) + + // ... + + // load string object onto the stack... + stloc stringLocalMarkedPinned + ldloc stringLocalMarkedPinned + conv.u + stloc ptrLocal + ldloc ptrLocal + brfalse.s PTR_NULL + + ldloc ptrLocal + call int32 [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::get_OffsetToStringData() + add + stloc ptrLocal + +PTR_NULL: + // code using the pointer in ptrLocal +``` + +Importantly, this sequence uses `conv.u` on a string object to convert a value of type `O` to a value of type `U` +(a.k.a. `nuint` or `UIntPtr`), giving the address of the object, then uses `RuntimeHelpers.OffsetToStringData` to +offset that to the start of the string data. + +New runtimes expose `GetPinnableReference()` on `string`, and if it's present, Roslyn will use that instead. However, +the versions of Mono that this is a problem with (such as the version Unity 5.x uses) expose the .NET 3.5 API surface +which does *not* include `GetPinnableReference()`. + +This fails because the JIT's importer has an incomplete implementation of `conv.u`. + +The relevant code is in `mini/method-to-ir.c`, in the `type_from_op` function: + +```c +// ... +switch (ins->opcode) { +// ... +case CEE_CONV_U: + ins->type = STACK_PTR; + switch (src1->type) { + case STACK_I4: + ins->opcode = OP_ICONV_TO_U; + break; + case STACK_PTR: + case STACK_MP: +#if SIZEOF_REGISTER == 8 + ins->opcode = OP_LCONV_TO_U; +#else + ins->opcode = OP_MOVE; +#endif + break; + case STACK_I8: + ins->opcode = OP_LCONV_TO_U; + break; + case STACK_R8: + ins->opcode = OP_FCONV_TO_U; + break; + } + break; +// ... +} +// ... +``` + +In the problematic case, `src1->type` is `STACK_OBJ`, which is not handled, and so `ins->opcode` remains the IL +opcode for `conv.u`, as opposed to a Mono IR opcode. This then causes an assertion failure in `mini/local-propagation.c` +in `mono_local_cprop`: + +```c +g_assert (ins->opcode > MONO_CEE_LAST); +``` + +Which, of course, fails, because `ins->opcode` is still `CEE_CONV_U`, which is less than `MONO_CEE_LAST`. + +### Workarounds + +1. Use an array, like `new char[] { /* ... */ }`. +2. Convert the string to a `ReadOnlySpan` first, like `str.AsSpan()`. diff --git a/docs/Utils/index.md b/docs/Utils/index.md new file mode 100644 index 00000000..4aaea02d --- /dev/null +++ b/docs/Utils/index.md @@ -0,0 +1,3 @@ +# MonoMod.Utils + +* [Using ModInterop](ModInterop.md) diff --git a/docs/Utils/toc.yml b/docs/Utils/toc.yml new file mode 100644 index 00000000..d299175f --- /dev/null +++ b/docs/Utils/toc.yml @@ -0,0 +1,4 @@ +href: index.md +items: +- name: Using ModInterop + href: ModInterop.md diff --git a/docs/docs.csproj b/docs/docs.csproj new file mode 100644 index 00000000..4d7b3159 --- /dev/null +++ b/docs/docs.csproj @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..cd2614cf --- /dev/null +++ b/docs/index.md @@ -0,0 +1,20 @@ +# Conceptual Documentation Index + +- [Debugging With MonoMod](Debugging.md) +- [MonoMod Configuration Switches](Switches.md) + +## Per-package + +- [MonoMod.Core](Core/index.md) +- [MonoMod.RuntimeDetour](RuntimeDetour/index.md) +- [MonoMod.RuntimeDetour.HookGen](RuntimeDetour.HookGen/index.md) +- [MonoMod.Utils](Utils/index.md) + +## Package READMEs + +- [MonoMod.Backports](README.Backports.md) +- [MonoMod.ILHelpers](README.ILHelpers.md) +- [MonoMod.Core](README.Core.md) +- [MonoMod.Patcher](README.Patcher.md) +- [MonoMod.RuntimeDetour](README.RuntimeDetour.md) +- [MonoMod.Utils](README.Utils.md) diff --git a/docs/toc.yml b/docs/toc.yml new file mode 100644 index 00000000..671fd9d6 --- /dev/null +++ b/docs/toc.yml @@ -0,0 +1,33 @@ +name: Conceptual Documentation +href: index.md +items: +- name: Debugging With MonoMod + href: Debugging.md + items: + - name: Configuration Switches + href: Switches.md +- name: Per-package + items: + - name: MonoMod.Core + href: Core/toc.yml + - name: MonoMod.RuntimeDetour + href: RuntimeDetour/toc.yml + - name: MonoMod.RuntimeDetour.HookGen + href: RuntimeDetour.HookGen/toc.yml + - name: MonoMod.Utils + href: Utils/toc.yml +- name: Package READMEs + items: + - name: MonoMod.Backports + href: README.Backports.md + - name: MonoMod.ILHelpers + href: README.ILHelpers.md + - name: MonoMod.Core + href: README.Core.md + - name: MonoMod.Patcher + href: README.Patcher.md + - name: MonoMod.RuntimeDetour + href: README.RuntimeDetour.md + - name: MonoMod.Utils + href: README.Utils.md +- href: RuntimeIssueNotes.md \ No newline at end of file diff --git a/external/iced.props b/external/iced.props index 73226c4f..b6f7645d 100644 --- a/external/iced.props +++ b/external/iced.props @@ -10,7 +10,7 @@ true false true - false + <_MMTargetFrameworks>$(TargetFrameworks) <_MMDefineConsts>$(DefineConstants) diff --git a/global.json b/global.json index 7ddc0f3f..980041e3 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,11 @@ { "sdk": { "allowPrerelease": true, - "rollForward": "latestMinor", - "version": "8.0.100" + "rollForward": "latestPatch", + "version": "8.0.301" + }, + "msbuild-sdks": { + "Microsoft.Build.NoTargets": "3.7.56", + "Microsoft.NET.Sdk.IL": "8.0.0" } } \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..f40e5a2c --- /dev/null +++ b/nuget.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Common/Common.csproj b/src/Common/Common.csproj index d58e8520..d6af450e 100644 --- a/src/Common/Common.csproj +++ b/src/Common/Common.csproj @@ -1,2 +1,2 @@  - \ No newline at end of file + \ No newline at end of file diff --git a/src/Common/UnsafeAlias.cs b/src/Common/UnsafeAlias.cs index 7d283c3c..29216bec 100644 --- a/src/Common/UnsafeAlias.cs +++ b/src/Common/UnsafeAlias.cs @@ -4,8 +4,8 @@ global using ilhelpers::MonoMod; -#if !NET6_0_OR_GREATER -// Any time we want to use Unsafe, we want ours, not the BCL's +#if !NET6_0_OR_GREATER && (NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET) +// Any time we want to use Unsafe, we want ours, not the BCL's. Note that we need these funky defs because the location of Unsafe moves between versions. // I would actually rather move the BCL assembly defining it into an alias, but that doesn't seem to be particularly viable global using Unsafe = ilhelpers::System.Runtime.CompilerServices.Unsafe; #else diff --git a/src/MonoMod.Backports/CompatibilitySuppressions.xml b/src/MonoMod.Backports/CompatibilitySuppressions.xml index 24315c06..8674ead1 100644 --- a/src/MonoMod.Backports/CompatibilitySuppressions.xml +++ b/src/MonoMod.Backports/CompatibilitySuppressions.xml @@ -4,219 +4,87 @@ CP0002 M:System.ValueTuple.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`1.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`2.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`3.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`4.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`5.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`6.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`7.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true CP0002 M:System.ValueTuple`8.#ctor - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`1.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`2.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`3.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`4.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`5.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`6.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`7.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple`8.#ctor - lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - true - - - CP0002 - M:System.ValueTuple.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`1.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`2.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`3.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`4.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`5.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`6.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`7.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - - - CP0002 - M:System.ValueTuple`8.#ctor - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll CP0002 M:System.HashCode.AddBytes(System.ReadOnlySpan{System.Byte}) - ref/netstandard2.0/MonoMod.Backports.dll - ref/netcoreapp2.1/MonoMod.Backports.dll + lib/netstandard2.0/MonoMod.Backports.dll + lib/netcoreapp2.1/MonoMod.Backports.dll CP0002 M:System.HashCode.AddBytes(System.ReadOnlySpan{System.Byte}) - ref/netstandard2.0/MonoMod.Backports.dll - ref/netstandard2.1/MonoMod.Backports.dll + lib/netstandard2.0/MonoMod.Backports.dll + lib/netstandard2.1/MonoMod.Backports.dll CP0008 T:System.Runtime.CompilerServices.TupleElementNamesAttribute - lib/net452/MonoMod.Backports.dll + lib/net35/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll - true - CP0008 - T:System.Runtime.CompilerServices.TupleElementNamesAttribute + CP1002 + mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes lib/net452/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll + lib/net452/MonoMod.Backports.dll true - - CP0008 - T:System.Runtime.CompilerServices.TupleElementNamesAttribute - ref/net35/MonoMod.Backports.dll - ref/net452/MonoMod.Backports.dll - CP1002 mscorlib, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e, Retargetable=Yes ref/net452/MonoMod.Backports.dll lib/net452/MonoMod.Backports.dll + true \ No newline at end of file diff --git a/src/MonoMod.Backports/Directory.Build.targets b/src/MonoMod.Backports/Directory.Build.targets index 34ae00e8..c9b9a8cf 100644 --- a/src/MonoMod.Backports/Directory.Build.targets +++ b/src/MonoMod.Backports/Directory.Build.targets @@ -2,22 +2,20 @@ - + true - - all;buildTransitive - all;buildTransitive - none - false - true - false - + + + + $([MSBuild]::NormalizePath('$(NuGetPackageRoot)', 'system.collections.immutable', '6.0.0')) + + diff --git a/src/MonoMod.Backports/MonoMod.Backports.csproj b/src/MonoMod.Backports/MonoMod.Backports.csproj index 47a43881..2de1a934 100644 --- a/src/MonoMod.Backports/MonoMod.Backports.csproj +++ b/src/MonoMod.Backports/MonoMod.Backports.csproj @@ -1,17 +1,19 @@  - $(TargetFrameworks);netstandard2.1;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1 + + $(BackportsTargetFrameworks) false - true + false + false false false - 1.1.0 - 1.0.0 + 1.1.2 + 1.1.0 @@ -27,6 +29,13 @@ or ('$(MMTFKind)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(MMTFVersion)','2.1')))">true + + + + ilhelpers + + + diff --git a/src/MonoMod.Backports/MonoMod.Backports/SRCS.Unsafe.cs b/src/MonoMod.Backports/MonoMod.Backports/SRCS.Unsafe.cs new file mode 100644 index 00000000..44cb4915 --- /dev/null +++ b/src/MonoMod.Backports/MonoMod.Backports/SRCS.Unsafe.cs @@ -0,0 +1,192 @@ +#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET +#define UNSAFE_IN_ILHELPERS +#endif + +extern alias ilhelpers; + +// Sometimes these global usings are unused. That's fine. +#pragma warning disable IDE0005 + +// Global usings +global using ilhelpers::MonoMod; + +#if UNSAFE_IN_ILHELPERS && !NET6_0_OR_GREATER +global using Unsafe = ilhelpers::System.Runtime.CompilerServices.Unsafe; +#else +global using Unsafe = System.Runtime.CompilerServices.Unsafe; +#endif + +#pragma warning restore IDE0005 + +#if UNSAFE_IN_ILHELPERS +// SRCS.Unsafe is defined in ILHelpers, so we want to define UnsafeRaw + a type-forwarder + +#if NET6_0_OR_GREATER +using ILImpl = System.Runtime.CompilerServices.Unsafe; +#else +using ILImpl = ilhelpers::System.Runtime.CompilerServices.Unsafe; +#endif + +using System; +using System.Runtime.CompilerServices; + +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(ILImpl))] + +namespace MonoMod.Backports.ILHelpers; + +[CLSCompliant(false)] +public static unsafe class UnsafeRaw +#else +// SRCS.Unsafe is defined here, so we want to define Unsafe + +using MonoMod.Backports; + +using ILImpl = ilhelpers::MonoMod.Backports.ILHelpers.UnsafeRaw; + +namespace System.Runtime.CompilerServices; + +[CLSCompliant(false)] +public static unsafe class Unsafe +#endif +{ + #region Direct forwarders +#nullable disable + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static T Read(void* source) => ILImpl.Read(source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static T ReadUnaligned(void* source) => ILImpl.ReadUnaligned(source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static T ReadUnaligned(ref byte source) => ILImpl.ReadUnaligned(ref source); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void Write(void* destination, T value) => ILImpl.Write(destination, value); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void WriteUnaligned(void* destination, T value) => ILImpl.WriteUnaligned(destination, value); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void WriteUnaligned(ref byte destination, T value) => ILImpl.WriteUnaligned(ref destination, value); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void Copy(void* destination, ref T source) => ILImpl.Copy(destination, ref source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void Copy(ref T destination, void* source) => ILImpl.Copy(ref destination, source); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void* AsPointer(ref T value) => ILImpl.AsPointer(ref value); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void SkipInit(out T value) => ILImpl.SkipInit(out value); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void CopyBlock(void* destination, void* source, uint byteCount) => ILImpl.CopyBlock(destination, source, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void CopyBlock(ref byte destination, ref byte source, uint byteCount) => ILImpl.CopyBlock(ref destination, ref source, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void CopyBlockUnaligned(void* destination, void* source, uint byteCount) => ILImpl.CopyBlockUnaligned(destination, source, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void CopyBlockUnaligned(ref byte destination, ref byte source, uint byteCount) => ILImpl.CopyBlockUnaligned(ref destination, ref source, byteCount); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void InitBlock(void* startAddress, byte value, uint byteCount) => ILImpl.InitBlock(startAddress, value, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void InitBlock(ref byte startAddress, byte value, uint byteCount) => ILImpl.InitBlock(ref startAddress, value, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void InitBlockUnaligned(void* startAddress, byte value, uint byteCount) => ILImpl.InitBlockUnaligned(startAddress, value, byteCount); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount) => ILImpl.InitBlockUnaligned(ref startAddress, value, byteCount); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static T As(object o) where T : class => ILImpl.As(o); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T AsRef(void* source) => ref ILImpl.AsRef(source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T AsRef(in T source) => ref ILImpl.AsRef(in source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref TTo As(ref TFrom source) => ref ILImpl.As(ref source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Unbox(object box) where T : struct => ref ILImpl.Unbox(box); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T AddByteOffset(ref T source, nint byteOffset) => ref ILImpl.AddByteOffset(ref source, byteOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T AddByteOffset(ref T source, nuint byteOffset) => ref ILImpl.AddByteOffset(ref source, byteOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T SubtractByteOffset(ref T source, nint byteOffset) => ref ILImpl.SubtractByteOffset(ref source, byteOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T SubtractByteOffset(ref T source, nuint byteOffset) => ref ILImpl.SubtractByteOffset(ref source, byteOffset); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static nint ByteOffset(ref T origin, ref T target) => ILImpl.ByteOffset(ref origin, ref target); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static bool AreSame(ref T left, ref T right) => ILImpl.AreSame(ref left, ref right); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static bool IsAddressGreaterThan(ref T left, ref T right) => ILImpl.IsAddressGreaterThan(ref left, ref right); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static bool IsAddressLessThan(ref T left, ref T right) => ILImpl.IsAddressLessThan(ref left, ref right); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static bool IsNullRef(ref T source) => ILImpl.IsNullRef(ref source); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T NullRef() => ref ILImpl.NullRef(); +#nullable enable + #endregion + +#if !UNSAFE_IN_ILHELPERS + // See docs/RuntimeIssueNotes.md. Until 2015, Mono returned incorrect values for the sizeof opcode when applied to a type parameter. + // To deal with this, we need to compute type size in another way, and return it as appropriate, specializing all of the below accordingly. + private static class PerTypeValues + { + public static readonly nint TypeSize = ComputeTypeSize(); + + private static nint ComputeTypeSize() + { + var array = new T[2]; + return ILImpl.ByteOffset(ref array[0], ref array[1]); + } + } + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static int SizeOf() => (int)PerTypeValues.TypeSize; + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, int elementOffset) => ref ILImpl.AddByteOffset(ref source, (nint)elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void* Add(void* source, int elementOffset) => (byte*)source + (elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, nint elementOffset) => ref ILImpl.AddByteOffset(ref source, elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, nuint elementOffset) => ref ILImpl.AddByteOffset(ref source, elementOffset * (nuint)PerTypeValues.TypeSize); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, int elementOffset) => ref ILImpl.SubtractByteOffset(ref source, (nint)elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void* Subtract(void* source, int elementOffset) => (byte*)source - (elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, nint elementOffset) => ref ILImpl.SubtractByteOffset(ref source, elementOffset * PerTypeValues.TypeSize); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, nuint elementOffset) => ref ILImpl.SubtractByteOffset(ref source, elementOffset * (nuint)PerTypeValues.TypeSize); + +#else + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static int SizeOf() => ILImpl.SizeOf(); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, int elementOffset) => ref ILImpl.Add(ref source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void* Add(void* source, int elementOffset) => ILImpl.Add(source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, nint elementOffset) => ref ILImpl.Add(ref source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Add(ref T source, nuint elementOffset) => ref ILImpl.Add(ref source, elementOffset); + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, int elementOffset) => ref ILImpl.Subtract(ref source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static void* Subtract(void* source, int elementOffset) => ILImpl.Subtract(source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, nint elementOffset) => ref ILImpl.Subtract(ref source, elementOffset); + [MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable] + public static ref T Subtract(ref T source, nuint elementOffset) => ref ILImpl.Subtract(ref source, elementOffset); + +#endif +} \ No newline at end of file diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Memory.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Memory.cs index f307c323..114b4f07 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Memory.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Memory.cs @@ -279,7 +279,7 @@ public Span Span // and then cast to a Memory. Such a cast can only be done with unsafe or marshaling code, // in which case that's the dangerous operation performed by the dev, and we're just following // suit here to make it work as best as possible. - return new Span(Unsafe.As>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length); + return new Span(s, (nint)RuntimeHelpers.OffsetToStringData, s.Length).Slice(_index, _length); } else if (_object != null) { diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/MemoryExtensions.Portable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/MemoryExtensions.Portable.cs index 9c065649..94ea033b 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/MemoryExtensions.Portable.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/MemoryExtensions.Portable.cs @@ -278,7 +278,7 @@ public static ReadOnlySpan AsSpan(this string? text) if (text == null) return default; - return new ReadOnlySpan(Unsafe.As>(text), StringAdjustment, text.Length); + return new ReadOnlySpan(text, (nint)RuntimeHelpers.OffsetToStringData, text.Length); } /// @@ -302,7 +302,7 @@ public static ReadOnlySpan AsSpan(this string? text, int start) if ((uint)start > (uint)text.Length) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); - return new ReadOnlySpan(Unsafe.As>(text), StringAdjustment + start * sizeof(char), text.Length - start); + return new ReadOnlySpan(text, (nint)RuntimeHelpers.OffsetToStringData + start * sizeof(char), text.Length - start); } /// @@ -327,7 +327,7 @@ public static ReadOnlySpan AsSpan(this string? text, int start, int length if ((uint)start > (uint)text.Length || (uint)length > (uint)(text.Length - start)) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); - return new ReadOnlySpan(Unsafe.As>(text), StringAdjustment + start * sizeof(char), length); + return new ReadOnlySpan(text, (nint)RuntimeHelpers.OffsetToStringData + start * sizeof(char), length); } /// Creates a new over the portion of the target string. @@ -383,19 +383,5 @@ public static ReadOnlyMemory AsMemory(this string? text, int start, int le return new ReadOnlyMemory(text, start, length); } - - internal static readonly nint StringAdjustment = MeasureStringAdjustment(); - - private static nint MeasureStringAdjustment() - { - string sampleString = "a"; - unsafe - { - fixed (char* pSampleString = sampleString) - { - return Unsafe.ByteOffset(ref Unsafe.As>(sampleString).Data, ref Unsafe.AsRef(pSampleString)); - } - } - } } } \ No newline at end of file diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Pinnable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Pinnable.cs deleted file mode 100644 index 84e7e1b5..00000000 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Pinnable.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; - -namespace System -{ - // - // This class exists solely so that arbitrary objects can be Unsafe-casted to it to get a ref to the start of the user data. - // - [StructLayout(LayoutKind.Sequential)] - [SuppressMessage("Performance", "CA1812", Justification = "Objects are unsafe-casted to this to be stored in Memory and Span")] - internal sealed class Pinnable - { - public T Data = default!; - } -} \ No newline at end of file diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlyMemory.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlyMemory.cs index 10178db3..c4801fee 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlyMemory.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlyMemory.cs @@ -196,7 +196,7 @@ public ReadOnlySpan Span else if (typeof(T) == typeof(char) && _object is string s) { Debug.Assert(_length >= 0); - return new ReadOnlySpan(Unsafe.As>(s), MemoryExtensions.StringAdjustment, s.Length).Slice(_index, _length); + return new ReadOnlySpan(s, (nint)RuntimeHelpers.OffsetToStringData, s.Length).Slice(_index, _length); } else if (_object != null) { diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlySpan.Portable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlySpan.Portable.cs index 50b15cb3..49525c36 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlySpan.Portable.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/ReadOnlySpan.Portable.cs @@ -33,7 +33,7 @@ public ReadOnlySpan(T[]? array) } _length = array.Length; - _pinnable = Unsafe.As>(array); + _pinnable = array; _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment; } @@ -63,7 +63,7 @@ public ReadOnlySpan(T[]? array, int start, int length) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; - _pinnable = Unsafe.As>(array); + _pinnable = array; _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment.Add(start); } @@ -97,7 +97,7 @@ public unsafe ReadOnlySpan(void* pointer, int length) // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan(Pinnable? pinnable, IntPtr byteOffset, int length) + internal ReadOnlySpan(object? pinnable, IntPtr byteOffset, int length) { Debug.Assert(length >= 0); @@ -122,10 +122,7 @@ public ref readonly T this[int index] if ((uint)index >= ((uint)_length)) ThrowHelper.ThrowIndexOutOfRangeException(); - if (_pinnable == null) - unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } - else - return ref Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); + return ref Unsafe.Add(ref DangerousGetPinnableReference(), index); } } @@ -138,11 +135,7 @@ public unsafe ref readonly T GetPinnableReference() { if (_length != 0) { - if (_pinnable == null) - { - return ref Unsafe.AsRef(_byteOffset.ToPointer()); - } - return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); + return ref DangerousGetPinnableReference(); } return ref Unsafe.AsRef(null); } @@ -205,7 +198,7 @@ public override string ToString() if (typeof(T) == typeof(char)) { // If this wraps a string and represents the full length of the string, just return the wrapped string. - if (_byteOffset == MemoryExtensions.StringAdjustment) + if (_byteOffset == (nint)RuntimeHelpers.OffsetToStringData) { object? obj = Unsafe.As(_pinnable); // minimize chances the compilers will optimize away the 'is' check if (obj is string str && _length == str.Length) @@ -276,22 +269,15 @@ public T[] ToArray() } /// - /// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead. /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [EditorBrowsable(EditorBrowsableState.Never)] - internal ref T DangerousGetPinnableReference() - { - if (_pinnable == null) - unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } - else - return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); - } + internal ref T DangerousGetPinnableReference() => + ref Unsafe.AddByteOffset(ref ILHelpers.ObjectAsRef(_pinnable), _byteOffset); // These expose the internal representation for Span-related apis use only. - internal Pinnable? Pinnable => _pinnable; + internal object? Pinnable => _pinnable; internal IntPtr ByteOffset => _byteOffset; // @@ -305,7 +291,7 @@ internal ref T DangerousGetPinnableReference() // _pinnable = null // _byteOffset = the pointer // - private readonly Pinnable? _pinnable; + private readonly object? _pinnable; private readonly IntPtr _byteOffset; private readonly int _length; } diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Runtime/InteropServices/MemoryMarshal.Portable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Runtime/InteropServices/MemoryMarshal.Portable.cs index 738a70ab..55757ca7 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Runtime/InteropServices/MemoryMarshal.Portable.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Runtime/InteropServices/MemoryMarshal.Portable.cs @@ -31,7 +31,7 @@ public static Span AsBytes(Span span) ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); int newLength = checked(span.Length * Unsafe.SizeOf()); - return new Span(Unsafe.As>(span.Pinnable), span.ByteOffset, newLength); + return new Span(span.Pinnable, span.ByteOffset, newLength); } /// @@ -53,7 +53,7 @@ public static ReadOnlySpan AsBytes(ReadOnlySpan span) ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(T)); int newLength = checked(span.Length * Unsafe.SizeOf()); - return new ReadOnlySpan(Unsafe.As>(span.Pinnable), span.ByteOffset, newLength); + return new ReadOnlySpan(span.Pinnable, span.ByteOffset, newLength); } /// Creates a from a . @@ -72,25 +72,13 @@ public static Memory AsMemory(ReadOnlyMemory memory) => /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// - public static ref T GetReference(Span span) - { - if (span.Pinnable == null) - unsafe { return ref Unsafe.AsRef(span.ByteOffset.ToPointer()); } - else - return ref Unsafe.AddByteOffset(ref span.Pinnable.Data, span.ByteOffset); - } + public static ref T GetReference(Span span) => ref span.DangerousGetPinnableReference(); /// /// Returns a reference to the 0th element of the ReadOnlySpan. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// - public static ref T GetReference(ReadOnlySpan span) - { - if (span.Pinnable == null) - unsafe { return ref Unsafe.AsRef(span.ByteOffset.ToPointer()); } - else - return ref Unsafe.AddByteOffset(ref span.Pinnable.Data, span.ByteOffset); - } + public static ref T GetReference(ReadOnlySpan span) => ref Unsafe.AsRef(in span.GetPinnableReference()); /// /// Casts a Span of one primitive type to another primitive type . @@ -114,7 +102,7 @@ public static Span Cast(Span span) ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(TTo)); int newLength = checked((int)((long)span.Length * Unsafe.SizeOf() / Unsafe.SizeOf())); - return new Span(Unsafe.As>(span.Pinnable), span.ByteOffset, newLength); + return new Span(span.Pinnable, span.ByteOffset, newLength); } /// @@ -139,7 +127,7 @@ public static ReadOnlySpan Cast(ReadOnlySpan span) ThrowHelper.ThrowArgumentException_InvalidTypeWithPointersNotSupported(typeof(TTo)); int newLength = checked((int)((long)span.Length * Unsafe.SizeOf() / Unsafe.SizeOf())); - return new ReadOnlySpan(Unsafe.As>(span.Pinnable), span.ByteOffset, newLength); + return new ReadOnlySpan(span.Pinnable, span.ByteOffset, newLength); } } } \ No newline at end of file diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Span.Portable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Span.Portable.cs index a9451b22..c7eb29c9 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Span.Portable.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/Span.Portable.cs @@ -35,7 +35,7 @@ public Span(T[]? array) ThrowHelper.ThrowArrayTypeMismatchException(); _length = array.Length; - _pinnable = Unsafe.As>(array); + _pinnable = array; _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment; } @@ -58,7 +58,7 @@ internal static Span Create(T[]? array, int start) IntPtr byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment.Add(start); int length = array.Length - start; - return new Span(pinnable: Unsafe.As>(array), byteOffset: byteOffset, length: length); + return new Span(pinnable: array, byteOffset: byteOffset, length: length); } /// @@ -89,7 +89,7 @@ public Span(T[]? array, int start, int length) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start); _length = length; - _pinnable = Unsafe.As>(array); + _pinnable = array; _byteOffset = SpanHelpers.PerTypeValues.ArrayAdjustment.Add(start); } @@ -123,7 +123,7 @@ public unsafe Span(void* pointer, int length) // Constructor for internal use only. [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Span(Pinnable? pinnable, IntPtr byteOffset, int length) + internal Span(object? pinnable, IntPtr byteOffset, int length) { Debug.Assert(length >= 0); @@ -148,10 +148,7 @@ public ref T this[int index] if ((uint)index >= ((uint)_length)) ThrowHelper.ThrowIndexOutOfRangeException(); - if (_pinnable == null) - unsafe { return ref Unsafe.Add(ref Unsafe.AsRef(_byteOffset.ToPointer()), index); } - else - return ref Unsafe.Add(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset), index); + return ref Unsafe.Add(ref DangerousGetPinnableReference(), index); } } @@ -160,17 +157,14 @@ public ref T this[int index] /// It can be used for pinning and is required to support the use of span within a fixed statement. /// [EditorBrowsable(EditorBrowsableState.Never)] - public unsafe ref T GetPinnableReference() + public ref T GetPinnableReference() { if (_length != 0) { - if (_pinnable == null) - { - return ref Unsafe.AsRef(_byteOffset.ToPointer()); - } - return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); + return ref DangerousGetPinnableReference(); } - return ref Unsafe.AsRef(null); + + return ref Unsafe.NullRef(); } /// @@ -195,9 +189,7 @@ public unsafe void Clear() } else { - ref byte b = ref Unsafe.As(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset)); - - SpanHelpers.ClearLessThanPointerSized(ref b, byteLength); + SpanHelpers.ClearLessThanPointerSized(ref Unsafe.As(ref DangerousGetPinnableReference()), byteLength); } } else @@ -232,15 +224,7 @@ public unsafe void Fill(T value) if (Unsafe.SizeOf() == 1) { byte fill = Unsafe.As(ref value); - if (_pinnable == null) - { - Unsafe.InitBlockUnaligned(_byteOffset.ToPointer(), fill, (uint)length); - } - else - { - ref byte r = ref Unsafe.As(ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset)); - Unsafe.InitBlockUnaligned(ref r, fill, (uint)length); - } + Unsafe.InitBlockUnaligned(ref Unsafe.As(ref DangerousGetPinnableReference()), fill, (uint)length); } else { @@ -401,22 +385,15 @@ public T[] ToArray() } /// - /// This method is obsolete, use System.Runtime.InteropServices.MemoryMarshal.GetReference instead. /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - [EditorBrowsable(EditorBrowsableState.Never)] - internal ref T DangerousGetPinnableReference() - { - if (_pinnable == null) - unsafe { return ref Unsafe.AsRef(_byteOffset.ToPointer()); } - else - return ref Unsafe.AddByteOffset(ref _pinnable.Data, _byteOffset); - } + internal ref T DangerousGetPinnableReference() => + ref Unsafe.AddByteOffset(ref ILHelpers.ObjectAsRef(_pinnable), _byteOffset); // These expose the internal representation for Span-related apis use only. - internal Pinnable? Pinnable => _pinnable; + internal object? Pinnable => _pinnable; internal IntPtr ByteOffset => _byteOffset; // @@ -430,7 +407,7 @@ internal ref T DangerousGetPinnableReference() // _pinnable = null // _byteOffset = the pointer // - private readonly Pinnable? _pinnable; + private readonly object? _pinnable; private readonly IntPtr _byteOffset; private readonly int _length; } diff --git a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/SpanHelpers.Portable.cs b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/SpanHelpers.Portable.cs index 662d8e41..82921870 100644 --- a/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/SpanHelpers.Portable.cs +++ b/src/MonoMod.Backports/System/Memory,is_fx,lt_core_2.1,lt_std_2.1/SpanHelpers.Portable.cs @@ -310,7 +310,7 @@ public static class PerTypeValues private static IntPtr MeasureArrayAdjustment() { T[] sampleArray = new T[1]; - return Unsafe.ByteOffset(ref Unsafe.As>(sampleArray).Data, ref sampleArray[0]); + return Unsafe.ByteOffset(ref ILHelpers.ObjectAsRef(sampleArray), ref sampleArray[0]); } } } diff --git a/src/MonoMod.Backports/System/NonVersionableAttribute.cs b/src/MonoMod.Backports/System/NonVersionableAttribute.cs new file mode 100644 index 00000000..7834361d --- /dev/null +++ b/src/MonoMod.Backports/System/NonVersionableAttribute.cs @@ -0,0 +1,7 @@ +namespace System +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal sealed class NonVersionableAttribute : Attribute + { + } +} diff --git a/src/MonoMod.Core/IDetourFactory.cs b/src/MonoMod.Core/IDetourFactory.cs index 2d8170b3..463c9744 100644 --- a/src/MonoMod.Core/IDetourFactory.cs +++ b/src/MonoMod.Core/IDetourFactory.cs @@ -79,9 +79,10 @@ public static class DetourFactory public static unsafe IDetourFactory Current { [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] - get => Helpers.GetOrInit(ref lazyCurrent, &CreateDefaultFactory); + get => Helpers.GetOrInit(ref lazyCurrent, createDefaultFactoryFunc); } + private static readonly Func createDefaultFactoryFunc = CreateDefaultFactory; private static PlatformTripleDetourFactory CreateDefaultFactory() => new(PlatformTriple.Current); diff --git a/src/MonoMod.Core/MonoMod.Core.csproj b/src/MonoMod.Core/MonoMod.Core.csproj index eca1e4c8..90d752d0 100644 --- a/src/MonoMod.Core/MonoMod.Core.csproj +++ b/src/MonoMod.Core/MonoMod.Core.csproj @@ -12,7 +12,7 @@ RuntimeDetour;detour;detours;$(PackageTags) - 1.1.0 + 1.1.2 1.0.0 true diff --git a/src/MonoMod.Core/Platforms/Architectures/x86Arch.cs b/src/MonoMod.Core/Platforms/Architectures/x86Arch.cs index 81284ad8..60999060 100644 --- a/src/MonoMod.Core/Platforms/Architectures/x86Arch.cs +++ b/src/MonoMod.Core/Platforms/Architectures/x86Arch.cs @@ -11,10 +11,11 @@ internal sealed class x86Arch : IArchitecture public ArchitectureFeature Features => ArchitectureFeature.CreateAltEntryPoint; private BytePatternCollection? lazyKnownMethodThunks; - public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, &CreateKnownMethodThunks); + public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, createKnownMethodThunksFunc); public IAltEntryFactory AltEntryFactory { get; } + private static readonly Func createKnownMethodThunksFunc = CreateKnownMethodThunks; private static BytePatternCollection CreateKnownMethodThunks() { const ushort An = BytePattern.SAnyValue; diff --git a/src/MonoMod.Core/Platforms/Architectures/x86_64Arch.cs b/src/MonoMod.Core/Platforms/Architectures/x86_64Arch.cs index 19e7400a..2de03bf3 100644 --- a/src/MonoMod.Core/Platforms/Architectures/x86_64Arch.cs +++ b/src/MonoMod.Core/Platforms/Architectures/x86_64Arch.cs @@ -13,10 +13,11 @@ internal sealed class x86_64Arch : IArchitecture public ArchitectureFeature Features => ArchitectureFeature.Immediate64 | ArchitectureFeature.CreateAltEntryPoint; private BytePatternCollection? lazyKnownMethodThunks; - public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, &CreateKnownMethodThunks); + public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, createKnownMethodThunksFunc); public IAltEntryFactory AltEntryFactory { get; } + private static readonly Func createKnownMethodThunksFunc = CreateKnownMethodThunks; private static BytePatternCollection CreateKnownMethodThunks() { const ushort An = BytePattern.SAnyValue; diff --git a/src/MonoMod.Core/Platforms/PlatformTriple.cs b/src/MonoMod.Core/Platforms/PlatformTriple.cs index 494130bd..d59e9ad0 100644 --- a/src/MonoMod.Core/Platforms/PlatformTriple.cs +++ b/src/MonoMod.Core/Platforms/PlatformTriple.cs @@ -118,8 +118,9 @@ public static ISystem CreateCurrentSystem() /// /// This is automatically constructed on first access, according to the values returned by . /// - public static unsafe PlatformTriple Current => Helpers.GetOrInitWithLock(ref lazyCurrent, lazyCurrentLock, &CreateCurrent); + public static unsafe PlatformTriple Current => Helpers.GetOrInitWithLock(ref lazyCurrent, lazyCurrentLock, createCurrentFunc); + private static readonly Func createCurrentFunc = CreateCurrent; private static PlatformTriple CreateCurrent() { var sys = CreateCurrentSystem(); diff --git a/src/MonoMod.Core/Platforms/Runtimes/Core21Runtime.cs b/src/MonoMod.Core/Platforms/Runtimes/Core21Runtime.cs index 29cd6eee..3cc67a84 100644 --- a/src/MonoMod.Core/Platforms/Runtimes/Core21Runtime.cs +++ b/src/MonoMod.Core/Platforms/Runtimes/Core21Runtime.cs @@ -39,11 +39,12 @@ public unsafe override void DisableInlining(MethodBase method) { } */ + private static readonly Func createJitHookHelpersFunc = CreateJitHookHelpers; private static JitHookHelpersHolder CreateJitHookHelpers(Core21Runtime self) => new(self); private readonly object sync = new(); private JitHookHelpersHolder? lazyJitHookHelpers; - protected unsafe JitHookHelpersHolder JitHookHelpers => Helpers.GetOrInitWithLock(ref lazyJitHookHelpers, sync, &CreateJitHookHelpers, this); + protected unsafe JitHookHelpersHolder JitHookHelpers => Helpers.GetOrInitWithLock(ref lazyJitHookHelpers, sync, createJitHookHelpersFunc, this); // src/inc/corinfo.h line 216 // 0ba106c8-81a0-407f-99a1-928448c1eb62 diff --git a/src/MonoMod.Core/Platforms/Runtimes/FxCoreBaseRuntime.cs b/src/MonoMod.Core/Platforms/Runtimes/FxCoreBaseRuntime.cs index 17df3f1c..dc471a80 100644 --- a/src/MonoMod.Core/Platforms/Runtimes/FxCoreBaseRuntime.cs +++ b/src/MonoMod.Core/Platforms/Runtimes/FxCoreBaseRuntime.cs @@ -305,7 +305,7 @@ public virtual void Compile(MethodBase method) { // if the method is a virtual method on a value type, we want to do something to compile the real // method instead of just the unboxing stub. The RuntimeMethodHandle we got from the MethodBase - // points to the unboxing stub. We could use knowlege of the runtime to get the non-unboxing stub + // points to the unboxing stub. We could use knowledge of the runtime to get the non-unboxing stub // MethodDesc, then create a RuntimeMethodHandle for it, however that may change per-runtime. // If we want to implement that, we can implement TryGetCacnonicalMethodHandle. diff --git a/src/MonoMod.ILHelpers.Patcher/MonoMod.ILHelpers.Patcher.csproj b/src/MonoMod.ILHelpers.Patcher/MonoMod.ILHelpers.Patcher.csproj index e40b480e..dc327f6e 100644 --- a/src/MonoMod.ILHelpers.Patcher/MonoMod.ILHelpers.Patcher.csproj +++ b/src/MonoMod.ILHelpers.Patcher/MonoMod.ILHelpers.Patcher.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 LatestMajor false diff --git a/src/MonoMod.ILHelpers.Patcher/Program.cs b/src/MonoMod.ILHelpers.Patcher/Program.cs index a8d1ed6a..79539338 100644 --- a/src/MonoMod.ILHelpers.Patcher/Program.cs +++ b/src/MonoMod.ILHelpers.Patcher/Program.cs @@ -1,5 +1,6 @@ using Mono.Cecil; using System; +using System.IO; [assembly: CLSCompliant(false)] @@ -12,11 +13,12 @@ var assemblyPath = args[0]; var verString = args[1]; var output = args.Length > 2 ? args[2] : null; +var hasSymbols = File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")); -using var module = ModuleDefinition.ReadModule(assemblyPath, new(ReadingMode.Deferred) +using var module = ModuleDefinition.ReadModule(assemblyPath, new(ReadingMode.Immediate) { ReadWrite = true, - ReadSymbols = true, + ReadSymbols = hasSymbols, }); if (module.RuntimeVersion == verString && output is null) { @@ -27,7 +29,7 @@ var writerParams = new WriterParameters() { DeterministicMvid = true, - WriteSymbols = true, + WriteSymbols = hasSymbols, Timestamp = null, }; diff --git a/src/MonoMod.ILHelpers/CompatibilitySuppressions.xml b/src/MonoMod.ILHelpers/CompatibilitySuppressions.xml index 3656039a..464a886f 100644 --- a/src/MonoMod.ILHelpers/CompatibilitySuppressions.xml +++ b/src/MonoMod.ILHelpers/CompatibilitySuppressions.xml @@ -1,31 +1,47 @@  - - CP0001 - T:System.Runtime.CompilerServices.Unsafe + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) + lib/net35/MonoMod.ILHelpers.dll + lib/net35/MonoMod.ILHelpers.dll + true + + + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) + lib/net452/MonoMod.ILHelpers.dll + lib/net452/MonoMod.ILHelpers.dll + true + + + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) lib/net5.0/MonoMod.ILHelpers.dll - lib/net6.0/MonoMod.ILHelpers.dll + lib/net5.0/MonoMod.ILHelpers.dll + true - CP1002 - System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null - left + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) + lib/net6.0/MonoMod.ILHelpers.dll lib/net6.0/MonoMod.ILHelpers.dll + true - CP1002 - System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null - left - lib/net6.0/MonoMod.ILHelpers.dll + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) + lib/net7.0/MonoMod.ILHelpers.dll + lib/net7.0/MonoMod.ILHelpers.dll true - CP1002 - System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0, Culture=neutral, PublicKeyToken=null - lib/net6.0/MonoMod.ILHelpers.dll - right + CP0002 + M:MonoMod.ILHelpers.UnboxAnyUnsafe``1(System.Object@) + lib/netstandard2.0/MonoMod.ILHelpers.dll + lib/netstandard2.0/MonoMod.ILHelpers.dll + true CP1002 diff --git a/src/MonoMod.ILHelpers/MonoMod.ILHelpers.il b/src/MonoMod.ILHelpers/MonoMod.ILHelpers.il index 8a76abc2..8b76173e 100644 --- a/src/MonoMod.ILHelpers/MonoMod.ILHelpers.il +++ b/src/MonoMod.ILHelpers/MonoMod.ILHelpers.il @@ -1,66 +1,70 @@ -#include "version.h" - -#ifdef NET35 -#define FUNC_ASSEMBLY "System.Core" -.assembly extern FUNC_ASSEMBLY -{ - .publickeytoken = CORE_PUBKEY_TOKEN - .ver 3:5:0:0 -} -#else -#define FUNC_ASSEMBLY "CORE_ASSEMBLY" -#endif - -.class public abstract auto ansi sealed beforefieldinit MonoMod.ILHelpers - extends [CORE_ASSEMBLY]System.Object -{ - .method public hidebysig static !!T TailCallDelegatePtr(native int source) cil managed aggressiveinlining - { - .maxstack 8 - ldarg.0 - tail. - calli !!T() - ret - } // end of method Unsafe::Read - - .method public hidebysig static !!T TailCallFunc(class [FUNC_ASSEMBLY]System.Func`1 func) cil managed aggressiveinlining - { - .maxstack 8 - ldarg.0 - tail. - callvirt instance !0 class [FUNC_ASSEMBLY]System.Func`1::Invoke() - ret - } - - .method public hidebysig static !!T& UnboxAnyUnsafe(object& 'box') cil managed aggressiveinlining - { - .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( - 01 00 02 00 00 - ) - - .maxstack 8 - .locals init ( - [0] !!T - ) - - // if (default(T) == null) - ldloca.s 0 - initobj !!T - ldloc.0 - box !!T - ldnull - ceq - brfalse ValType - - // it's a reference type, so we just return the ref like Unsafe.As - ldarg.0 - ret - - ValType: - // it's a value type, so we want to unbox and return the ref - ldarg.0 - ldind.ref - unbox !!T - ret - } +#include "version.h" + +#ifdef NET35 +#define FUNC_ASSEMBLY "System.Core" +.assembly extern FUNC_ASSEMBLY +{ + .publickeytoken = CORE_PUBKEY_TOKEN + .ver 3:5:0:0 +} +#else +#define FUNC_ASSEMBLY "CORE_ASSEMBLY" +#endif + +.class public abstract auto ansi sealed beforefieldinit MonoMod.ILHelpers + extends [CORE_ASSEMBLY]System.Object +{ + .method public hidebysig static !!T TailCallDelegatePtr(native int source) cil managed aggressiveinlining + { + .maxstack 8 + ldarg.0 + //tail. // The tail prefix on calli seems to be considered by many tools to be invalid. + calli !!T() + ret + } // end of method Unsafe::Read + + .method public hidebysig static !!T TailCallFunc(class [FUNC_ASSEMBLY]System.Func`1 func) cil managed aggressiveinlining + { + .maxstack 8 + ldarg.0 + tail. + callvirt instance !0 class [FUNC_ASSEMBLY]System.Func`1::Invoke() + ret + } + + .method public hidebysig static !!T& ObjectAsRef(object obj) cil managed aggressiveinlining + { + .maxstack 1 + .locals ( + object pinned pin +#ifndef netcoreapp + , !!T** refPtr + , !!T& finalRef +#endif + ) + + // pin obj + ldarg obj + stloc pin + +#ifdef netcoreapp + // return ref *Unsafe.BitCast(pin); + ldloc pin + conv.u +#else + // see docs/RuntimeIssueNotes.md - "`fixed` on strings in old Mono" for why this is necessary + // T* ptr = *(T**)(&pin); + ldloca pin + conv.u + stloc refPtr + ldloc refPtr + ldind.i + // return Unsafe.AsRef(ptr); + // see the comments inside that function for why don't just immediately ret + stloc finalRef + ldloc finalRef +#endif + + ret + } } \ No newline at end of file diff --git a/src/MonoMod.ILHelpers/MonoMod.ILHelpers.ilproj b/src/MonoMod.ILHelpers/MonoMod.ILHelpers.ilproj index 0578737d..6ad3de21 100644 --- a/src/MonoMod.ILHelpers/MonoMod.ILHelpers.ilproj +++ b/src/MonoMod.ILHelpers/MonoMod.ILHelpers.ilproj @@ -1,11 +1,40 @@ - - + - A collection of IL helpers for MonoMod, including a backport of System.Runtime.CompilerServices.Unsafe. + DO NOT REFERENCE THIS PACKAGE DIRECTLY! Reference MonoMod.Backports instead. + + + $(BackportsTargetFrameworks) - 1.0.1 + 1.1.0 1.0.0 + + + + + + + + + + \ No newline at end of file diff --git a/src/MonoMod.ILHelpers/System.Runtime.CompilerServices.Unsafe.il b/src/MonoMod.ILHelpers/System.Runtime.CompilerServices.Unsafe.il index e087c714..99f9440e 100644 --- a/src/MonoMod.ILHelpers/System.Runtime.CompilerServices.Unsafe.il +++ b/src/MonoMod.ILHelpers/System.Runtime.CompilerServices.Unsafe.il @@ -7,10 +7,23 @@ // =============== CLASS MEMBERS DECLARATION =================== +.assembly extern MonoMod.Backports { } + +#ifdef NETSTANDARD2_1_OR_GREATER + #define UNSAFE_ILHELPERS +#endif +#ifdef NETCOREAPP1_0_OR_GREATER + #define UNSAFE_ILHELPERS +#endif + #ifdef NET6_0_OR_GREATER #ifdef NET6_0 -.assembly extern System.Runtime.CompilerServices.Unsafe { .ver CORE_ASSEMBLY_VERSION } +.assembly extern System.Runtime.CompilerServices.Unsafe +{ + .publickeytoken = CORE_PUBKEY_TOKEN + .ver CORE_ASSEMBLY_VERSION +} #endif .class extern forwarder System.Runtime.CompilerServices.Unsafe @@ -24,10 +37,21 @@ #endif } +.class extern forwarder MonoMod.Backports.ILHelpers.UnsafeRaw { .assembly extern MonoMod.Backports } + #else // TODO: nullable annotations (ugh) +#ifdef UNSAFE_ILHELPERS +// we define SRCS.Unsafe in ILHelpers, and forward UnsafeRaw to Backports, which forwards back here. +.class extern forwarder MonoMod.Backports.ILHelpers.UnsafeRaw { .assembly extern MonoMod.Backports } .class public abstract auto ansi sealed beforefieldinit System.Runtime.CompilerServices.Unsafe +#else +// we define SRCS.Unsafe in Backports to handle some runtime issues, forward to it here, +// and Backports uses some of UnsafeRaw to actually implement it +.class extern forwarder System.Runtime.CompilerServices.Unsafe { .assembly extern MonoMod.Backports } +.class public abstract auto ansi sealed beforefieldinit MonoMod.Backports.ILHelpers.UnsafeRaw +#endif extends [CORE_ASSEMBLY]System.Object { .method public hidebysig static !!T Read(void* source) cil managed aggressiveinlining @@ -324,6 +348,10 @@ .method public hidebysig static !!T& Add(!!T& source, native int elementOffset) cil managed aggressiveinlining { .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( + 01 00 00 00 + ) .maxstack 8 ldarg.0 ldarg.1 @@ -335,11 +363,11 @@ .method public hidebysig static !!T& Add(!!T& source, native uint elementOffset) cil managed aggressiveinlining { + .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .param [2] .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( 01 00 00 00 ) - .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .maxstack 8 ldarg.0 ldarg.1 @@ -352,6 +380,10 @@ .method public hidebysig static !!T& AddByteOffset(!!T& source, native int byteOffset) cil managed aggressiveinlining { .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( + 01 00 00 00 + ) .maxstack 8 ldarg.0 ldarg.1 @@ -361,11 +393,11 @@ .method public hidebysig static !!T& AddByteOffset(!!T& source, native uint byteOffset) cil managed aggressiveinlining { + .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .param [2] .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( 01 00 00 00 ) - .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .maxstack 8 ldarg.0 ldarg.1 @@ -402,6 +434,10 @@ .method public hidebysig static !!T& Subtract(!!T& source, native int elementOffset) cil managed aggressiveinlining { .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( + 01 00 00 00 + ) .maxstack 8 ldarg.0 ldarg.1 @@ -413,11 +449,11 @@ .method public hidebysig static !!T& Subtract(!!T& source, native uint elementOffset) cil managed aggressiveinlining { + .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .param [2] .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( 01 00 00 00 ) - .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .maxstack 8 ldarg.0 ldarg.1 @@ -430,6 +466,10 @@ .method public hidebysig static !!T& SubtractByteOffset(!!T& source, native int byteOffset) cil managed aggressiveinlining { .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) + .param [2] + .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( + 01 00 00 00 + ) .maxstack 8 ldarg.0 ldarg.1 @@ -439,11 +479,11 @@ .method public hidebysig static !!T& SubtractByteOffset(!!T& source, native uint byteOffset) cil managed aggressiveinlining { + .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .param [2] .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( 01 00 00 00 ) - .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) .maxstack 8 ldarg.0 ldarg.1 @@ -454,6 +494,10 @@ .method public hidebysig static native int ByteOffset(!!T& origin, !!T& target) cil managed aggressiveinlining { .custom instance void System.Runtime.Versioning.NonVersionableAttribute::.ctor() = ( 01 00 00 00 ) + .param [0] + .custom instance void System.Runtime.CompilerServices.NativeIntegerAttribute::.ctor() = ( + 01 00 00 00 + ) .maxstack 8 ldarg.1 ldarg.0 diff --git a/src/MonoMod.RuntimeDetour/MonoMod.RuntimeDetour.csproj b/src/MonoMod.RuntimeDetour/MonoMod.RuntimeDetour.csproj index 1da1cf94..6caf5eb9 100644 --- a/src/MonoMod.RuntimeDetour/MonoMod.RuntimeDetour.csproj +++ b/src/MonoMod.RuntimeDetour/MonoMod.RuntimeDetour.csproj @@ -13,7 +13,7 @@ true - 25.1.0 + 25.1.2 25.0.0 diff --git a/src/MonoMod.SourceGen.Internal/AnalyzerReleases.Unshipped.md b/src/MonoMod.SourceGen.Internal/AnalyzerReleases.Unshipped.md index d9dd9bf2..2a4c8bb7 100644 --- a/src/MonoMod.SourceGen.Internal/AnalyzerReleases.Unshipped.md +++ b/src/MonoMod.SourceGen.Internal/AnalyzerReleases.Unshipped.md @@ -8,4 +8,7 @@ Rule ID | Category | Severity | Notes MM.ILOverload.BadKind | | Error | ILOverloadGenerator MM.ILOverload.NoFile | | Error | ILOverloadGenerator MM0001 | Build | Warning | AssemblyInfoGenerator -MM0002 | Build | Warning | AssemblyInfoGenerator \ No newline at end of file +MM0002 | Build | Warning | AssemblyInfoGenerator +MMA001 | RuntimeIssues | Error | DoNotPinStrings +MMA002 | RuntimeIssues | Warning | DoNotSizeofGenerics +MMA003 | RuntimeIssues | Warning | DoNotSizeofGenerics \ No newline at end of file diff --git a/src/MonoMod.SourceGen.Internal/Analyzers/DoNotPinStrings.cs b/src/MonoMod.SourceGen.Internal/Analyzers/DoNotPinStrings.cs new file mode 100644 index 00000000..62a05fda --- /dev/null +++ b/src/MonoMod.SourceGen.Internal/Analyzers/DoNotPinStrings.cs @@ -0,0 +1,65 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; + +namespace MonoMod.SourceGen.Internal.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DoNotPinStrings : DiagnosticAnalyzer + { + private static readonly DiagnosticDescriptor PinningStringsIsDangerous = new( + "MMA001", + "Do not pin strings, as it may crash some older Mono runtimes", + "Do not pin strings, as it may crash some older Mono runtimes (see docs/RuntimeIssueNotes.md). Pin a span instead.", + "RuntimeIssues", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(PinningStringsIsDangerous); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", + Justification = "Roslyn always passes a non-null context")] + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics); // if generated code pins strings, we still want to report it + + context.RegisterOperationAction(ctx => + { + var op = (IVariableDeclaratorOperation)ctx.Operation; + + if (!op.Symbol.IsFixed) + { + // we only care about fixed variables + return; + } + + var initializer = op.GetVariableInitializer()?.Value; + if (initializer is null) + { + // no initializer, nothing to do + return; + } + + if (initializer.IsImplicit) + { + initializer = initializer.ChildOperations.Any() ? initializer.ChildOperations.First() : null; + } + + if (initializer is null || initializer.Type is null) + { + // no initializer, nothing to do + return; + } + + if (initializer.Type.SpecialType is SpecialType.System_String) + { + // the initializer of the fixed variable is a string, report it + ctx.ReportDiagnostic(Diagnostic.Create(PinningStringsIsDangerous, initializer.Syntax.GetLocation())); + } + + }, OperationKind.VariableDeclarator); + } + } +} diff --git a/src/MonoMod.SourceGen.Internal/Analyzers/DoNotSizeofGenerics.cs b/src/MonoMod.SourceGen.Internal/Analyzers/DoNotSizeofGenerics.cs new file mode 100644 index 00000000..3ac96f5f --- /dev/null +++ b/src/MonoMod.SourceGen.Internal/Analyzers/DoNotSizeofGenerics.cs @@ -0,0 +1,50 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using System.Collections.Immutable; + +namespace MonoMod.SourceGen.Internal.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class DoNotSizeofGenerics : DiagnosticAnalyzer + { + private static readonly DiagnosticDescriptor DoNotSizeofGeneric = new( + "MMA002", + "Do not use the sizeof() operator on a generic parameter", + "On some old Mono runtimes, sizeof(T) always returns sizeof(IntPtr). See docs/RuntimeIssueNotes.md.", + "RuntimeIssues", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + private static readonly DiagnosticDescriptor DoNotUseUnsafeSizeOf = new( + "MMA003", + "Do not use Unsafe.SizeOf()", + "On some old Mono runtimes, the sizeof opcode always returns sizeof(IntPtr) on generic parameters, " + + "which Unsafe.SizeOf() always has.. See docs/RuntimeIssueNotes.md.", + "RuntimeIssues", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(DoNotSizeofGeneric, DoNotUseUnsafeSizeOf); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", + Justification = "Roslyn always passes a non-null context")] + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics); + + // normal sizeof operator + context.RegisterOperationAction(ctx => + { + var sizeofOp = (ISizeOfOperation)ctx.Operation; + + if (sizeofOp.TypeOperand.TypeKind is TypeKind.TypeParameter) + { + ctx.ReportDiagnostic(Diagnostic.Create(DoNotSizeofGeneric, sizeofOp.Syntax.GetLocation())); + } + }, OperationKind.SizeOf); + } + } +} diff --git a/src/MonoMod.SourceGen.Internal/MonoMod.SourceGen.Internal.csproj b/src/MonoMod.SourceGen.Internal/MonoMod.SourceGen.Internal.csproj index 9359747d..b1fdecc9 100644 --- a/src/MonoMod.SourceGen.Internal/MonoMod.SourceGen.Internal.csproj +++ b/src/MonoMod.SourceGen.Internal/MonoMod.SourceGen.Internal.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/MonoMod.UnitTest/MonoMod.UnitTest.csproj b/src/MonoMod.UnitTest/MonoMod.UnitTest.csproj index d7930b3c..473eebd8 100644 --- a/src/MonoMod.UnitTest/MonoMod.UnitTest.csproj +++ b/src/MonoMod.UnitTest/MonoMod.UnitTest.csproj @@ -12,17 +12,18 @@ $(NoWarn);CA1852 - net6.0;net5.0;net7.0;net8.0;net46;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1 - Library + net6.0;net5.0;net7.0;net8.0;net462;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1 skip annotations false + Library + 2.4.2 $(XunitVersion) - [2.*,2.4.3] + [2.*,2.4.3] @@ -33,11 +34,13 @@ + @@ -51,11 +54,11 @@ - + @@ -67,8 +70,11 @@ runtime; build; native; contentfiles; analyzers all - + + + + \ No newline at end of file diff --git a/src/MonoMod.Utils/DMDGenerators/DMDEmit.EmitCallSite.cs b/src/MonoMod.Utils/DMDGenerators/DMDEmit.EmitCallSite.cs index 9e705963..a30ace63 100644 --- a/src/MonoMod.Utils/DMDGenerators/DMDEmit.EmitCallSite.cs +++ b/src/MonoMod.Utils/DMDGenerators/DMDEmit.EmitCallSite.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using CallSite = Mono.Cecil.CallSite; namespace MonoMod.Utils @@ -49,7 +49,7 @@ private static readonly MethodInfo? mDynamicMethod_AddRef .GetType("System.Reflection.Emit.DynamicScope")?.GetField("m_tokens", BindingFlags.NonPublic | BindingFlags.Instance); // Based on https://referencesource.microsoft.com/#mscorlib/system/reflection/mdimport.cs,74bfbae3c61889bc - private static readonly Type?[] CorElementTypes = new Type?[] { + private static readonly Type?[] CorElementTypes = [ null, // END typeof(void), // VOID typeof(bool), // BOOL @@ -80,7 +80,7 @@ private static readonly MethodInfo? mDynamicMethod_AddRef null, // FNPTR typeof(object), // OBJECT // all others don't have specific types associated - }; + ]; private abstract class TokenCreator { @@ -133,266 +133,274 @@ public override int GetTokenForSig(byte[] sig) // I assume, however, that we can't use SignatureHelper here because it is horribly broken on some (probably older) mono builds. } - internal static void _EmitCallSite(DynamicMethod dm, ILGenerator il, System.Reflection.Emit.OpCode opcode, CallSite csite) + private abstract class CallSiteEmitter { - /* The mess in this method is heavily based off of the code available at the following links: - * https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicmethod.cs#L791 - * https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicilgenerator.cs#L353 - * https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/DynamicMethod.cs#L411 - * https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/ILGenerator.cs#L800 - * https://github.com/dotnet/coreclr/blob/0fbd855e38bc3ec269479b5f6bf561dcfd67cbb6/src/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs#L57 - */ + public abstract void EmitCallSite(DynamicMethod dm, ILGenerator il, OpCode opcode, CallSite csite); + } - TokenCreator tokenCreator = DynamicMethod_AddRef is not null - ? new MonoTokenCreator(dm) : new NetTokenCreator(il); + private sealed class NetCallSiteEmitter : CallSiteEmitter + { + public override void EmitCallSite(DynamicMethod dm, ILGenerator il, OpCode opcode, CallSite csite) + { + /* The mess in this method is heavily based off of the code available at the following links: + * https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicmethod.cs#L791 + * https://github.com/Microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/reflection/emit/dynamicilgenerator.cs#L353 + * https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/DynamicMethod.cs#L411 + * https://github.com/mono/mono/blob/82e573122a55482bf6592f36f819597238628385/mcs/class/corlib/System.Reflection.Emit/ILGenerator.cs#L800 + * https://github.com/dotnet/coreclr/blob/0fbd855e38bc3ec269479b5f6bf561dcfd67cbb6/src/System.Private.CoreLib/src/System/Reflection/Emit/SignatureHelper.cs#L57 + */ - var signature = new byte[32]; - var currSig = 0; - var sizeLoc = -1; + TokenCreator tokenCreator = DynamicMethod_AddRef is not null + ? new MonoTokenCreator(dm) : new NetTokenCreator(il); - // This expects a MdSigCallingConvention - AddData((byte)csite.CallingConvention); - sizeLoc = currSig++; + var signature = new byte[32]; + var currSig = 0; + var sizeLoc = -1; - var modReq = new List(); - var modOpt = new List(); + // We're emitting a StandAloneMethodSig - ResolveWithModifiers(csite.ReturnType, out var returnType, out var returnTypeModReq, out var returnTypeModOpt, modReq, modOpt); - AddArgument(returnType, returnTypeModReq, returnTypeModOpt); + AddData(((byte)csite.CallingConvention) | (csite.HasThis ? 0x20 : 0) | (csite.ExplicitThis ? 0x40 : 0)); + sizeLoc = currSig++; - foreach (var param in csite.Parameters) - { - if (param.ParameterType.IsSentinel) - AddElementType(0x41 /* CorElementType.Sentinel */); + var modReq = new List(); + var modOpt = new List(); - if (param.ParameterType.IsPinned) - { - AddElementType(0x45 /* CorElementType.Pinned */); - // AddArgument(param.ParameterType.ResolveReflection()); - // continue; - } + ResolveWithModifiers(csite.ReturnType, out var returnType, out var returnTypeModReq, out var returnTypeModOpt, modReq, modOpt); + AddArgument(returnType, returnTypeModReq, returnTypeModOpt); - ResolveWithModifiers(param.ParameterType, out var paramType, out var paramTypeModReq, out var paramTypeModOpt, modReq, modOpt); - AddArgument(paramType, paramTypeModReq, paramTypeModOpt); - } + foreach (var param in csite.Parameters) + { + if (param.ParameterType.IsSentinel) + AddElementType(0x41 /* CorElementType.Sentinel */); - AddElementType(0x00 /* CorElementType.End */); - - // For most signatures, this will set the number of elements in a byte which we have reserved for it. - // However, if we have a field signature, we don't set the length and return. - // If we have a signature with more than 128 arguments, we can't just set the number of elements, - // we actually have to allocate more space (e.g. shift everything in the array one or more spaces to the - // right. We do this by making a copy of the array and leaving the correct number of blanks. This new - // array is now set to be m_signature and we use the AddData method to set the number of elements properly. - // The forceCopy argument can be used to force SetNumberOfSignatureElements to make a copy of - // the array. This is useful for GetSignature which promises to trim the array to be the correct size anyway. - - byte[] temp; - int newSigSize; - var currSigHolder = currSig; - - // We need to have more bytes for the size. Figure out how many bytes here. - // Since we need to copy anyway, we're just going to take the cost of doing a - // new allocation. - if (csite.Parameters.Count < 0x80) - { - newSigSize = 1; - } - else if (csite.Parameters.Count < 0x4000) - { - newSigSize = 2; - } - else - { - newSigSize = 4; - } + if (param.ParameterType.IsPinned) + { + AddElementType(0x45 /* CorElementType.Pinned */); + // AddArgument(param.ParameterType.ResolveReflection()); + // continue; + } - // Allocate the new array. - temp = new byte[currSig + newSigSize - 1]; + ResolveWithModifiers(param.ParameterType, out var paramType, out var paramTypeModReq, out var paramTypeModOpt, modReq, modOpt); + AddArgument(paramType, paramTypeModReq, paramTypeModOpt); + } - // Copy the calling convention. The calling convention is always just one byte - // so we just copy that byte. Then copy the rest of the array, shifting everything - // to make room for the new number of elements. - temp[0] = signature[0]; - Buffer.BlockCopy(signature, sizeLoc + 1, temp, sizeLoc + newSigSize, currSigHolder - (sizeLoc + 1)); - signature = temp; + AddElementType(0x00 /* CorElementType.End */); + + // For most signatures, this will set the number of elements in a byte which we have reserved for it. + // However, if we have a field signature, we don't set the length and return. + // If we have a signature with more than 128 arguments, we can't just set the number of elements, + // we actually have to allocate more space (e.g. shift everything in the array one or more spaces to the + // right. We do this by making a copy of the array and leaving the correct number of blanks. This new + // array is now set to be m_signature and we use the AddData method to set the number of elements properly. + // The forceCopy argument can be used to force SetNumberOfSignatureElements to make a copy of + // the array. This is useful for GetSignature which promises to trim the array to be the correct size anyway. + + byte[] temp; + int newSigSize; + var currSigHolder = currSig; + + // We need to have more bytes for the size. Figure out how many bytes here. + // Since we need to copy anyway, we're just going to take the cost of doing a + // new allocation. + if (csite.Parameters.Count < 0x80) + { + newSigSize = 1; + } + else if (csite.Parameters.Count < 0x4000) + { + newSigSize = 2; + } + else + { + newSigSize = 4; + } - //Use the AddData method to add the number of elements appropriately compressed. - currSig = sizeLoc; - AddData(csite.Parameters.Count); - currSig = currSigHolder + (newSigSize - 1); + // Allocate the new array. + temp = new byte[currSig + newSigSize - 1]; - // This case will only happen if the user got the signature through - // InternalGetSignature first and then called GetSignature. - if (signature.Length > currSig) - { - temp = new byte[currSig]; - Array.Copy(signature, temp, currSig); + // Copy the calling convention. The calling convention is always just one byte + // so we just copy that byte. Then copy the rest of the array, shifting everything + // to make room for the new number of elements. + temp[0] = signature[0]; + Buffer.BlockCopy(signature, sizeLoc + 1, temp, sizeLoc + newSigSize, currSigHolder - (sizeLoc + 1)); signature = temp; - } - // Emit. + //Use the AddData method to add the number of elements appropriately compressed. + currSig = sizeLoc; + AddData(csite.Parameters.Count); + currSig = currSigHolder + (newSigSize - 1); - if (_ILGen_emit_int != null) - { - // Mono - _ILGen_make_room!.Invoke(il, new object[] { 6 }); - _ILGen_ll_emit!.Invoke(il, new object[] { opcode }); - _ILGen_emit_int!.Invoke(il, new object[] { tokenCreator.GetTokenForSig(signature) }); - } - else - { - // .NET - _ILGen_EnsureCapacity!.Invoke(il, new object[] { 7 }); - _ILGen_InternalEmit!.Invoke(il, new object[] { opcode }); - - // The only IL instruction that has VarPop behaviour, that takes a - // Signature token as a parameter is calli. Pop the parameters and - // the native function pointer. To be conservative, do not pop the - // this pointer since this information is not easily derived from - // SignatureHelper. - if (opcode.StackBehaviourPop == System.Reflection.Emit.StackBehaviour.Varpop) + // This case will only happen if the user got the signature through + // InternalGetSignature first and then called GetSignature. + if (signature.Length > currSig) { - // Pop the arguments and native function pointer off the stack. - _ILGen_UpdateStackSize!.Invoke(il, new object[] { opcode, -csite.Parameters.Count - 1 }); + temp = new byte[currSig]; + Array.Copy(signature, temp, currSig); + signature = temp; } - _ILGen_PutInteger4!.Invoke(il, new object[] { tokenCreator.GetTokenForSig(signature) }); - } + // Emit. - void AddArgument(Type clsArgument, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers) - { - if (optionalCustomModifiers != null) - foreach (var t in optionalCustomModifiers) - InternalAddTypeToken(tokenCreator.GetTokenForType(t), 0x20 /* CorElementType.CModOpt */); + if (_ILGen_emit_int != null) + { + // Mono + _ILGen_make_room!.Invoke(il, new object[] { 6 }); + _ILGen_ll_emit!.Invoke(il, new object[] { opcode }); + _ILGen_emit_int!.Invoke(il, new object[] { tokenCreator.GetTokenForSig(signature) }); + } + else + { + // .NET + _ILGen_EnsureCapacity!.Invoke(il, new object[] { 7 }); + _ILGen_InternalEmit!.Invoke(il, new object[] { opcode }); + + // The only IL instruction that has VarPop behaviour, that takes a + // Signature token as a parameter is calli. Pop the parameters and + // the native function pointer. To be conservative, do not pop the + // this pointer since this information is not easily derived from + // SignatureHelper. + if (opcode.StackBehaviourPop == System.Reflection.Emit.StackBehaviour.Varpop) + { + // Pop the arguments and native function pointer off the stack. + _ILGen_UpdateStackSize!.Invoke(il, new object[] { opcode, -csite.Parameters.Count - 1 }); + } - if (requiredCustomModifiers != null) - foreach (var t in requiredCustomModifiers) - InternalAddTypeToken(tokenCreator.GetTokenForType(t), 0x1F /* CorElementType.CModReqd */); + _ILGen_PutInteger4!.Invoke(il, new object[] { tokenCreator.GetTokenForSig(signature) }); + } - AddOneArgTypeHelper(clsArgument); - } + void AddArgument(Type clsArgument, Type[] requiredCustomModifiers, Type[] optionalCustomModifiers) + { + if (optionalCustomModifiers != null) + foreach (var t in optionalCustomModifiers) + InternalAddTypeToken(tokenCreator.GetTokenForType(t), 0x20 /* CorElementType.CModOpt */); - void AddData(int data) - { - // A managed representation of CorSigCompressData; + if (requiredCustomModifiers != null) + foreach (var t in requiredCustomModifiers) + InternalAddTypeToken(tokenCreator.GetTokenForType(t), 0x1F /* CorElementType.CModReqd */); - if (currSig + 4 > signature!.Length) - { - signature = ExpandArray(signature); + AddOneArgTypeHelper(clsArgument); } - if (data <= 0x7F) + void AddData(int data) { - signature[currSig++] = (byte)(data & 0xFF); - } - else if (data <= 0x3FFF) - { - signature[currSig++] = (byte)((data >> 8) | 0x80); - signature[currSig++] = (byte)(data & 0xFF); + // A managed representation of CorSigCompressData; + + if (currSig + 4 > signature!.Length) + { + signature = ExpandArray(signature); + } + + if (data <= 0x7F) + { + signature[currSig++] = (byte)(data & 0xFF); + } + else if (data <= 0x3FFF) + { + signature[currSig++] = (byte)((data >> 8) | 0x80); + signature[currSig++] = (byte)(data & 0xFF); + } + else if (data <= 0x1FFFFFFF) + { + signature[currSig++] = (byte)((data >> 24) | 0xC0); + signature[currSig++] = (byte)((data >> 16) & 0xFF); + signature[currSig++] = (byte)((data >> 8) & 0xFF); + signature[currSig++] = (byte)((data) & 0xFF); + } + else + { + throw new ArgumentException("Integer or token was too large to be encoded."); + } } - else if (data <= 0x1FFFFFFF) + + byte[] ExpandArray(byte[] inArray, int requiredLength = -1) { - signature[currSig++] = (byte)((data >> 24) | 0xC0); - signature[currSig++] = (byte)((data >> 16) & 0xFF); - signature[currSig++] = (byte)((data >> 8) & 0xFF); - signature[currSig++] = (byte)((data) & 0xFF); + if (requiredLength < inArray.Length) + requiredLength = inArray.Length * 2; + + var outArray = new byte[requiredLength]; + Buffer.BlockCopy(inArray, 0, outArray, 0, inArray.Length); + return outArray; } - else + + void AddElementType(byte cvt) { - throw new ArgumentException("Integer or token was too large to be encoded."); - } - } + // Adds an element to the signature. A managed represenation of CorSigCompressElement + if (currSig + 1 > signature.Length) + signature = ExpandArray(signature); - byte[] ExpandArray(byte[] inArray, int requiredLength = -1) - { - if (requiredLength < inArray.Length) - requiredLength = inArray.Length * 2; + signature[currSig++] = cvt; + } - var outArray = new byte[requiredLength]; - Buffer.BlockCopy(inArray, 0, outArray, 0, inArray.Length); - return outArray; - } + void AddToken(int token) + { + // A managed represenation of CompressToken + // Pulls the token appart to get a rid, adds some appropriate bits + // to the token and then adds this to the signature. - void AddElementType(byte cvt) - { - // Adds an element to the signature. A managed represenation of CorSigCompressElement - if (currSig + 1 > signature.Length) - signature = ExpandArray(signature); + var rid = (token & 0x00FFFFFF); //This is RidFromToken; + var type = (token & unchecked((int)0xFF000000)); //This is TypeFromToken; - signature[currSig++] = cvt; - } + if (rid > 0x3FFFFFF) + { + // token is too big to be compressed + throw new ArgumentException("Integer or token was too large to be encoded."); + } - void AddToken(int token) - { - // A managed represenation of CompressToken - // Pulls the token appart to get a rid, adds some appropriate bits - // to the token and then adds this to the signature. + rid = (rid << 2); - var rid = (token & 0x00FFFFFF); //This is RidFromToken; - var type = (token & unchecked((int)0xFF000000)); //This is TypeFromToken; + // TypeDef is encoded with low bits 00 + // TypeRef is encoded with low bits 01 + // TypeSpec is encoded with low bits 10 + if (type == 0x01000000 /* MetadataTokenType.TypeRef */) + { + //if type is mdtTypeRef + rid |= 0x1; + } + else if (type == 0x1b000000 /* MetadataTokenType.TypeSpec */) + { + //if type is mdtTypeSpec + rid |= 0x2; + } - if (rid > 0x3FFFFFF) - { - // token is too big to be compressed - throw new ArgumentException("Integer or token was too large to be encoded."); + AddData(rid); } - rid = (rid << 2); - - // TypeDef is encoded with low bits 00 - // TypeRef is encoded with low bits 01 - // TypeSpec is encoded with low bits 10 - if (type == 0x01000000 /* MetadataTokenType.TypeRef */) + void InternalAddTypeToken(int clsToken, byte CorType) { - //if type is mdtTypeRef - rid |= 0x1; - } - else if (type == 0x1b000000 /* MetadataTokenType.TypeSpec */) - { - //if type is mdtTypeSpec - rid |= 0x2; + // Add a type token into signature. CorType will be either CorElementType.Class or CorElementType.ValueType + AddElementType(CorType); + AddToken(clsToken); } - AddData(rid); - } - - void InternalAddTypeToken(int clsToken, byte CorType) - { - // Add a type token into signature. CorType will be either CorElementType.Class or CorElementType.ValueType - AddElementType(CorType); - AddToken(clsToken); - } - - void AddOneArgTypeHelper(Type clsArgument) { AddOneArgTypeHelperWorker(clsArgument, false); } - void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst) - { - if (clsArgument.IsGenericType && (!clsArgument.IsGenericTypeDefinition || !lastWasGenericInst)) + void AddOneArgTypeHelper(Type clsArgument) { AddOneArgTypeHelperWorker(clsArgument, false); } + void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst) { - AddElementType(0x15 /* CorElementType.GenericInst */); + if (clsArgument.IsGenericType && (!clsArgument.IsGenericTypeDefinition || !lastWasGenericInst)) + { + AddElementType(0x15 /* CorElementType.GenericInst */); - AddOneArgTypeHelperWorker(clsArgument.GetGenericTypeDefinition(), true); + AddOneArgTypeHelperWorker(clsArgument.GetGenericTypeDefinition(), true); - var genargs = clsArgument.GetGenericArguments(); + var genargs = clsArgument.GetGenericArguments(); - AddData(genargs.Length); + AddData(genargs.Length); - foreach (var t in genargs) - AddOneArgTypeHelper(t); - } - else if (clsArgument.IsByRef) - { - AddElementType(0x10 /* CorElementType.ByRef */); - clsArgument = clsArgument.GetElementType() ?? clsArgument; - AddOneArgTypeHelper(clsArgument); - } - else if (clsArgument.IsPointer) - { - AddElementType(0x0F /* CorElementType.Ptr */); - AddOneArgTypeHelper(clsArgument.GetElementType() ?? clsArgument); - } - else if (clsArgument.IsArray) - { + foreach (var t in genargs) + AddOneArgTypeHelper(t); + } + else if (clsArgument.IsByRef) + { + AddElementType(0x10 /* CorElementType.ByRef */); + clsArgument = clsArgument.GetElementType() ?? clsArgument; + AddOneArgTypeHelper(clsArgument); + } + else if (clsArgument.IsPointer) + { + AddElementType(0x0F /* CorElementType.Ptr */); + AddOneArgTypeHelper(clsArgument.GetElementType() ?? clsArgument); + } + else if (clsArgument.IsArray) + { #if false if (clsArgument.IsArray && clsArgument == clsArgument.GetElementType().MakeArrayType()) { // .IsSZArray unavailable. AddElementType(0x1D /* CorElementType.SzArray */); @@ -400,71 +408,165 @@ void AddOneArgTypeHelperWorker(Type clsArgument, bool lastWasGenericInst) AddOneArgTypeHelper(clsArgument.GetElementType()); } else #endif - { - AddElementType(0x14 /* CorElementType.Array */); + { + AddElementType(0x14 /* CorElementType.Array */); - AddOneArgTypeHelper(clsArgument.GetElementType() ?? clsArgument); + AddOneArgTypeHelper(clsArgument.GetElementType() ?? clsArgument); - // put the rank information - var rank = clsArgument.GetArrayRank(); - AddData(rank); // rank - AddData(0); // upper bounds - AddData(rank); // lower bound - for (var i = 0; i < rank; i++) - AddData(0); + // put the rank information + var rank = clsArgument.GetArrayRank(); + AddData(rank); // rank + AddData(0); // upper bounds + AddData(rank); // lower bound + for (var i = 0; i < rank; i++) + AddData(0); + } } - } - else - { - // This isn't 100% accurate, but... oh well. - byte type = 0; // 0 is reserved anyway. - - for (var i = 0; i < CorElementTypes.Length; i++) + else { - if (clsArgument == CorElementTypes[i]) + // This isn't 100% accurate, but... oh well. + byte type = 0; // 0 is reserved anyway. + + for (var i = 0; i < CorElementTypes.Length; i++) { - type = (byte)i; - break; + if (clsArgument == CorElementTypes[i]) + { + type = (byte)i; + break; + } } - } - if (type == 0) - { - if (clsArgument == typeof(object)) + if (type == 0) + { + if (clsArgument == typeof(object)) + { + type = 0x1C /* CorElementType.Object */; + } + else if (clsArgument.IsValueType) + { + type = 0x11 /* CorElementType.ValueType */; + } + else + { + // Let's hope for the best. + type = 0x12 /* CorElementType.Class */; + } + } + + if (type <= 0x0E /* CorElementType.String */ || + type == 0x16 /* CorElementType.TypedByRef */ || + type == 0x18 /* CorElementType.I */ || + type == 0x19 /* CorElementType.U */ || + type == 0x1C /* CorElementType.Object */ + ) { - type = 0x1C /* CorElementType.Object */; + AddElementType(type); } else if (clsArgument.IsValueType) { - type = 0x11 /* CorElementType.ValueType */; + InternalAddTypeToken(tokenCreator.GetTokenForType(clsArgument), 0x11 /* CorElementType.ValueType */); } else { - // Let's hope for the best. - type = 0x12 /* CorElementType.Class */; + InternalAddTypeToken(tokenCreator.GetTokenForType(clsArgument), 0x12 /* CorElementType.Class */); } } + } + } + } - if (type <= 0x0E /* CorElementType.String */ || - type == 0x16 /* CorElementType.TypedByRef */ || - type == 0x18 /* CorElementType.I */ || - type == 0x19 /* CorElementType.U */ || - type == 0x1C /* CorElementType.Object */ - ) - { - AddElementType(type); - } - else if (clsArgument.IsValueType) - { - InternalAddTypeToken(tokenCreator.GetTokenForType(clsArgument), 0x11 /* CorElementType.ValueType */); - } - else - { - InternalAddTypeToken(tokenCreator.GetTokenForType(clsArgument), 0x12 /* CorElementType.Class */); - } + private sealed class MonoCallSiteEmitter : CallSiteEmitter + { + private FieldInfo SigHelper_callConv; + private FieldInfo SigHelper_unmanagedCallConv; + private FieldInfo SigHelper_arguments; + private FieldInfo SigHelper_modreqs; + private FieldInfo SigHelper_modopts; + + public MonoCallSiteEmitter() + { + var callConv = typeof(SignatureHelper).GetField("callConv", BindingFlags.Instance | BindingFlags.NonPublic); + var unmanagedCallConv = typeof(SignatureHelper).GetField("unmanagedCallConv", BindingFlags.Instance | BindingFlags.NonPublic); + var arguments = typeof(SignatureHelper).GetField("arguments", BindingFlags.Instance | BindingFlags.NonPublic); + var modreqs = typeof(SignatureHelper).GetField("modreqs", BindingFlags.Instance | BindingFlags.NonPublic); + var modopts = typeof(SignatureHelper).GetField("modopts", BindingFlags.Instance | BindingFlags.NonPublic); + + // if we hit this ctor, we should be running on Mono, which should mean these are all present + Helpers.Assert(callConv is not null); + Helpers.Assert(unmanagedCallConv is not null); + Helpers.Assert(arguments is not null); + Helpers.Assert(modreqs is not null); + Helpers.Assert(modopts is not null); + + SigHelper_callConv = callConv; + SigHelper_unmanagedCallConv = unmanagedCallConv; + SigHelper_arguments = arguments; + SigHelper_modreqs = modreqs; + SigHelper_modopts = modopts; + } + + public override void EmitCallSite(DynamicMethod dm, ILGenerator il, OpCode opcode, CallSite csite) + { + // On Mono, when its processing the tokens for a CallSite, it explicitly looks for a SignatureHelper, and so we CANNOT pass in + // a manually constructed signature. At all. Which sucks. It means that, on older Mono that have half-implemented SignatureHelpers, + // there's nothing we can do. + + var modReq = new List(); + var modOpt = new List(); + + // note: with the Mono signature helper, we can't represent modifiers on the return type + ResolveWithModifiers(csite.ReturnType, out var rawRetType, out _, out _, modReq, modOpt); + + // there's not a standard, public API for the metadata callconv field either, so we have to set it manually + var sigHelper = SignatureHelper.GetMethodSigHelper(CallingConventions.Standard, rawRetType); + + var arguments = new Type[csite.Parameters.Count]; + var modreqs = new Type[csite.Parameters.Count][]; + var modopts = new Type[csite.Parameters.Count][]; + + var managedCallConv = csite.CallingConvention switch + { + MethodCallingConvention.VarArg => CallingConventions.VarArgs, + _ => CallingConventions.Standard, + }; + if (csite.HasThis) managedCallConv |= CallingConventions.HasThis; + if (csite.ExplicitThis) managedCallConv |= CallingConventions.ExplicitThis; + + var unmanagedCallConv = csite.CallingConvention switch + { + MethodCallingConvention.C => CallingConvention.Cdecl, + MethodCallingConvention.StdCall => CallingConvention.StdCall, + MethodCallingConvention.ThisCall => CallingConvention.ThisCall, + MethodCallingConvention.FastCall => CallingConvention.FastCall, + _ => (CallingConvention)0, + }; + + for (var i = 0; i < csite.Parameters.Count; i++) + { + var param = csite.Parameters[i]; + + ResolveWithModifiers(param.ParameterType, out arguments[i], out modreqs[i], out modopts[i], modReq, modOpt); } + + // fill the signature helper + SigHelper_callConv.SetValue(sigHelper, managedCallConv); + SigHelper_unmanagedCallConv.SetValue(sigHelper, unmanagedCallConv); + SigHelper_arguments.SetValue(sigHelper, arguments); + SigHelper_modreqs.SetValue(sigHelper, modreqs); + SigHelper_modopts.SetValue(sigHelper, modopts); + + // emit the sighelper + _ILGen_make_room!.Invoke(il, new object[] { 6 }); + _ILGen_ll_emit!.Invoke(il, new object[] { opcode }); + _ILGen_emit_int!.Invoke(il, new object[] { DynamicMethod_AddRef!(dm, sigHelper) }); } + } + private static readonly CallSiteEmitter callSiteEmitter = DynamicMethod_AddRef is not null ? new MonoCallSiteEmitter() : new NetCallSiteEmitter(); + + internal static void _EmitCallSite(DynamicMethod dm, ILGenerator il, OpCode opcode, CallSite csite) + { + callSiteEmitter.EmitCallSite(dm, il, opcode, csite); } } diff --git a/src/MonoMod.Utils/DynDll.Backend.cs b/src/MonoMod.Utils/DynDll.Backend.cs index d47bd478..c8a3f548 100644 --- a/src/MonoMod.Utils/DynDll.Backend.cs +++ b/src/MonoMod.Utils/DynDll.Backend.cs @@ -181,7 +181,7 @@ protected override unsafe bool TryOpenLibraryCore(string? name, Assembly assembl } else { - fixed (char* pName = name) + fixed (char* pName = name.AsSpan()) { handle = result = Interop.Windows.LoadLibraryW((ushort*)pName); } diff --git a/src/MonoMod.Utils/DynamicReferenceManager.cs b/src/MonoMod.Utils/DynamicReferenceManager.cs index ce08140c..9396bfbd 100644 --- a/src/MonoMod.Utils/DynamicReferenceManager.cs +++ b/src/MonoMod.Utils/DynamicReferenceManager.cs @@ -114,7 +114,7 @@ private static unsafe DataScope AllocReferenceStruct(in [MethodImpl(MethodImplOptionsEx.AggressiveOptimization)] public static DataScope AllocReference(in T? value, out DynamicReferenceCell cellRef) { - if (default(T) == null) + if (!typeof(T).IsValueType) { return AllocReferenceClass(Unsafe.As(ref Unsafe.AsRef(in value)), out cellRef); } @@ -197,14 +197,14 @@ private static Cell GetCell(DynamicReferenceCell cellRef) { case RefValueCell: { - Helpers.Assert(default(T) == null); + Helpers.Assert(!typeof(T).IsValueType); var c = Unsafe.As(cell); Helpers.Assert(c.Value is null or T); return ref Unsafe.As(ref c.Value!); } case ValueTypeCell: { - Helpers.Assert(default(T) != null); + Helpers.Assert(typeof(T).IsValueType); var c = (ValueCell)cell; return ref c.Value; } diff --git a/src/MonoMod.Utils/Extensions.Unsafe.cs b/src/MonoMod.Utils/Extensions.Unsafe.cs index 54eeeeef..5df3b9e9 100644 --- a/src/MonoMod.Utils/Extensions.Unsafe.cs +++ b/src/MonoMod.Utils/Extensions.Unsafe.cs @@ -80,7 +80,7 @@ public static IntPtr GetLdftnPointer(this MethodBase m) lock (_GetLdftnPointerCache) { - return (_GetLdftnPointerCache[m] = dmd.Generate().CreateDelegate>() as Func)(); + return (_GetLdftnPointerCache[m] = dmd.Generate().CreateDelegate>())(); } } diff --git a/src/MonoMod.Utils/Helpers.cs b/src/MonoMod.Utils/Helpers.cs index 96e736fc..5f4fb919 100644 --- a/src/MonoMod.Utils/Helpers.cs +++ b/src/MonoMod.Utils/Helpers.cs @@ -24,22 +24,22 @@ public static void Swap(ref T a, ref T b) [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] public static unsafe bool Has(this T value, T flag) where T : struct, Enum { - if (sizeof(T) == sizeof(long)) + if (Unsafe.SizeOf() == sizeof(long)) { var flagVal = Unsafe.As(ref flag); return (Unsafe.As(ref value) & flagVal) == flagVal; } - else if (sizeof(T) == sizeof(int)) + else if (Unsafe.SizeOf() == sizeof(int)) { var flagVal = Unsafe.As(ref flag); return (Unsafe.As(ref value) & flagVal) == flagVal; } - else if (sizeof(T) == sizeof(short)) + else if (Unsafe.SizeOf() == sizeof(short)) { var flagVal = Unsafe.As(ref flag); return (Unsafe.As(ref value) & flagVal) == flagVal; } - else if (sizeof(T) == sizeof(byte)) + else if (Unsafe.SizeOf() == sizeof(byte)) { var flagVal = Unsafe.As(ref flag); return (Unsafe.As(ref value) & flagVal) == flagVal; @@ -157,22 +157,48 @@ private static void ThrowAssertionFailed(ref AssertionInterpolatedStringHandler } #region GetOrInit* + private static class FuncInvokeHolder + { + public static readonly Func, T> InvokeFunc = static f => f(); + } + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] + public static T GetOrInit(ref T? location, Func init) where T : class + { + if (location is not null) + return location; + return InitializeValue(ref location, FuncInvokeHolder.InvokeFunc, init); + } + + [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] + public static T GetOrInitWithLock(ref T? location, object @lock, Func init) where T : class + { + if (location is not null) + return location; + return InitializeValueWithLock(ref location, @lock, FuncInvokeHolder.InvokeFunc, init); + } + [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] - public unsafe static T GetOrInit(ref T? location, Func init) where T : class + public static T GetOrInit(ref T? location, Func init, TParam param) where T : class { + ThrowIfArgumentNull(init); if (location is not null) return location; - return InitializeValue(ref location, &ILHelpers.TailCallFunc, init); + return InitializeValue(ref location, init, param); } [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] - public unsafe static T GetOrInitWithLock(ref T? location, object @lock, Func init) where T : class + public static T GetOrInitWithLock(ref T? location, object @lock, Func init, TParam param) where T : class { + ThrowIfArgumentNull(init); if (location is not null) return location; - return InitializeValueWithLock(ref location, @lock, &ILHelpers.TailCallFunc, init); + return InitializeValueWithLock(ref location, @lock, init, param); } + /// + /// This overload may not work on some older Mono implementations, which do not have good function pointer support. + /// [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] public unsafe static T GetOrInit(ref T? location, delegate* init) where T : class { @@ -181,6 +207,10 @@ public unsafe static T GetOrInit(ref T? location, delegate* init) where T return InitializeValue(ref location, &ILHelpers.TailCallDelegatePtr, (IntPtr)init); } + + /// + /// This overload may not work on some older Mono implementations, which do not have good function pointer support. + /// [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] public unsafe static T GetOrInitWithLock(ref T? location, object @lock, delegate* init) where T : class { @@ -189,6 +219,9 @@ public unsafe static T GetOrInitWithLock(ref T? location, object @lock, deleg return InitializeValueWithLock(ref location, @lock, &ILHelpers.TailCallDelegatePtr, (IntPtr)init); } + /// + /// This overload may not work on some older Mono implementations, which do not have good function pointer support. + /// [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] public unsafe static T GetOrInit(ref T? location, delegate* init, TParam obj) where T : class { @@ -197,6 +230,9 @@ public unsafe static T GetOrInit(ref T? location, delegate* + /// This overload may not work on some older Mono implementations, which do not have good function pointer support. + /// [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] public unsafe static T GetOrInitWithLock(ref T? location, object @lock, delegate* init, TParam obj) where T : class { @@ -212,6 +248,13 @@ private unsafe static T InitializeValue(ref T? location, delegate*(ref T? location, Func init, TParam obj) where T : class + { + _ = Interlocked.CompareExchange(ref location, init(obj), null); + return location!; + } + [MethodImpl(MethodImplOptionsEx.NoInlining)] private unsafe static T InitializeValueWithLock(ref T? location, object @lock, delegate* init, TParam obj) where T : class { @@ -222,6 +265,17 @@ private unsafe static T InitializeValueWithLock(ref T? location, obje return location = init(obj); } } + + [MethodImpl(MethodImplOptionsEx.NoInlining)] + private unsafe static T InitializeValueWithLock(ref T? location, object @lock, Func init, TParam obj) where T : class + { + lock (@lock) + { + if (location is not null) + return location; + return location = init(obj); + } + } #endregion [MethodImpl(MethodImplOptionsEx.AggressiveInlining)] diff --git a/src/MonoMod.Utils/MonoMod.Utils.csproj b/src/MonoMod.Utils/MonoMod.Utils.csproj index 1b58b7a4..84c5a57d 100644 --- a/src/MonoMod.Utils/MonoMod.Utils.csproj +++ b/src/MonoMod.Utils/MonoMod.Utils.csproj @@ -9,7 +9,7 @@ enable false - 25.0.4 + 25.0.6 25.0.0 diff --git a/src/MonoMod.Utils/PlatformDetection.cs b/src/MonoMod.Utils/PlatformDetection.cs index fc3e5a9f..d7eff378 100644 --- a/src/MonoMod.Utils/PlatformDetection.cs +++ b/src/MonoMod.Utils/PlatformDetection.cs @@ -398,7 +398,7 @@ private static unsafe bool CheckWine() if (env == "FALSE") return false; - fixed (char* pNtdll = "ntdll.dll") + fixed (char* pNtdll = "ntdll.dll".AsSpan()) { var ntdll = Interop.Windows.GetModuleHandleW((ushort*)pNtdll); if (ntdll != Interop.Windows.HMODULE.NULL && ntdll != Interop.Windows.HMODULE.INVALID_VALUE) diff --git a/src/MonoMod.Utils/ReflectionHelper.FixReflectionCache.cs b/src/MonoMod.Utils/ReflectionHelper.FixReflectionCache.cs index b887663b..772dd288 100644 --- a/src/MonoMod.Utils/ReflectionHelper.FixReflectionCache.cs +++ b/src/MonoMod.Utils/ReflectionHelper.FixReflectionCache.cs @@ -7,18 +7,17 @@ namespace MonoMod.Utils { public static partial class ReflectionHelper { - // .NET Framework can break member ordering if using Module.Resolve* on certain members. private static readonly object?[] _CacheGetterArgs = { /* MemberListType.All */ 0, /* name apparently always null? */ null }; - private static Type t_RuntimeType = + // Note: on older Mono, RuntimeType doesn't exist. That's fine; it means there's no cache to fix. + private static Type? t_RuntimeType = typeof(Type).Assembly - .GetType("System.RuntimeType") - ?? throw new InvalidOperationException("Could not find RuntimeType"); + .GetType("System.RuntimeType"); private static Type? t_RuntimeTypeCache = - t_RuntimeType.GetNestedType("RuntimeTypeCache", BindingFlags.Public | BindingFlags.NonPublic); + t_RuntimeType?.GetNestedType("RuntimeTypeCache", BindingFlags.Public | BindingFlags.NonPublic); private static PropertyInfo? p_RuntimeType_Cache = t_RuntimeTypeCache == null ? null : diff --git a/tools/Common.CS.targets b/tools/Common.CS.targets index 1b51c87a..4eccb1bc 100644 --- a/tools/Common.CS.targets +++ b/tools/Common.CS.targets @@ -48,8 +48,12 @@ Shared/%(Filename)%(Extension) + + + - + false false diff --git a/tools/Common.IL.targets b/tools/Common.IL.targets index 31e8eba7..60debbaf 100644 --- a/tools/Common.IL.targets +++ b/tools/Common.IL.targets @@ -16,7 +16,7 @@ Reference="false" ReferenceOutputAssembly="false" OutputItemType="ILVersionPatcher" Private="false" Pack="false" - SetTargetFramework="TargetFramework=net6.0" + SetTargetFramework="TargetFramework=net8.0" SkipGetTargetFrameworkProperties="true" /> diff --git a/tools/Common.props b/tools/Common.props index 86d1b347..42a70888 100644 --- a/tools/Common.props +++ b/tools/Common.props @@ -12,6 +12,7 @@ net6.0;net5.0;net7.0;net8.0;netstandard2.0;net35;net452 + $(TargetFrameworks);netstandard2.1;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1 @@ -30,10 +31,8 @@ $(MMRootPath)docs\ - $(MMArtifactsPath)packages\$(Configuration)\ - $(MMArtifactsPath)obj\$(MSBuildProjectName)\ - $(BaseIntermediateOutputPath)$(Configuration)\ - $(MMArtifactsPath)bin\$(MSBuildProjectName)\ + true + $(MMArtifactsPath) true false @@ -43,6 +42,10 @@ true false false + + + false + true diff --git a/tools/Common.targets b/tools/Common.targets index 91dbe439..d69f5ce5 100644 --- a/tools/Common.targets +++ b/tools/Common.targets @@ -7,5 +7,20 @@ + + + + <_PropertiesToGet Remove="@(_PropertiesToGet)" /> + + + + + <_PropertiesToGet Include="$(PropertyNames)" /> + + + + + + diff --git a/tools/NuGet.props b/tools/NuGet.props index 07acb596..2642f90e 100644 --- a/tools/NuGet.props +++ b/tools/NuGet.props @@ -22,10 +22,6 @@ false - - - - diff --git a/tools/NuGet.targets b/tools/NuGet.targets index 8c6e1236..353e39e1 100644 --- a/tools/NuGet.targets +++ b/tools/NuGet.targets @@ -30,7 +30,7 @@ - + README.md @@ -40,6 +40,11 @@ + + + + + @@ -78,6 +83,7 @@ + \ No newline at end of file diff --git a/tools/tools.csproj b/tools/tools.csproj index e00ea0ef..4d7b3159 100644 --- a/tools/tools.csproj +++ b/tools/tools.csproj @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/tools/windbg-memlog.js b/tools/windbg-memlog.js index 81e469fe..f389e8d4 100644 --- a/tools/windbg-memlog.js +++ b/tools/windbg-memlog.js @@ -1,4 +1,11 @@ -"use strict"; +"use strict"; + +// To dump the memory log to a .tmp file +// .scriptrun +// +// To get navigable version of the log +// .scriptload // +// dx -g @$scriptContents.GetMMLog() function initializeScript() { @@ -11,6 +18,29 @@ function initializeScript() return [new host.apiVersionSupport(1, 7)]; } +function invokeScript() +{ + var log = GetMMLog(); + + var dbgOutput = host.diagnostics.debugLog; + + var file = host.namespace.Debugger.Utility.FileSystem.CreateTempFile(); + var textWriter = host.namespace.Debugger.Utility.FileSystem.CreateTextWriter(file); + + dbgOutput("Dumping Memory log to tmp file\n"); + + for (const e of log) { + if (e.Index > 0 && e.Index % 1000 == 0) + dbgOutput(e.Index + "...\n"); + + textWriter.WriteLine("[" + e.Time + "] [" + e.Source + "] " + e.Level + ": " + e.Message); + } + + file.Close(); + + dbgOutput("Dumped MonoMod Log to: " + file.Path); +} + function GetMMLog() { var ctl = host.namespace.Debugger.Utility.Control; @@ -235,5 +265,5 @@ function formatDateTime(dt) { var date = isUtc ? new Date(Date.UTC(year, month - 1, day, hours, minutes, seconds, millis)) : new Date(year, month - 1, day, hours, minutes, seconds, millis); - return date.toLocaleString(); + return date.toLocaleString().replace(/[^\w:/\\ ]/g, ""); } \ No newline at end of file