diff --git a/.dockerignore b/.dockerignore index a802e98..bb58282 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,10 @@ ci/docker/Dockerfile ci/docker/Dockerfile.base ci/build-image.sh -integration-test/output \ No newline at end of file +integration-test/output +net-ssa-cli/obj +net-ssa-lib/obj +unit-tests/obj +net-ssa-cli/bin +net-ssa-lib/bin +unit-tests/bin diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8943c93..7f8600b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -145,7 +145,7 @@ jobs: dotnet test --verbosity normal - name: Generate nuget packages run: | - ./ci/nuget-pack.sh "${{ steps.gitversion.outputs.AssemblySemVer }}" "${{ steps.gitversion.outputs.AssemblySemVer }}" "${{ steps.gitversion.outputs.InformationalVersion }}" "${{ steps.gitversion.outputs.AssemblySemVer }}" + ./ci/nuget-pack.sh "${{ steps.gitversion.outputs.AssemblySemVer }}" "${{ steps.gitversion.outputs.AssemblySemVer }}" "${{ steps.gitversion.outputs.InformationalVersion }}" "${{ steps.gitversion.outputs.AssemblySemVer }}" "build/bin/net-ssa/package" dotnet nuget push build/bin/net-ssa/package/net-ssa-lib.${{ steps.gitversion.outputs.NuGetVersionV2 }}.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json mirror: diff --git a/ci/build.sh b/ci/build.sh new file mode 100755 index 0000000..f5e01c2 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +CURRENT_DIR=$(dirname "$(readlink -f "$0")") + +pushd $CURRENT_DIR/.. + +rm -rf net-ssa-cli/obj net-ssa-lib/obj unit-tests/obj net-ssa-cli/bin net-ssa-lib/bin unit-tests/bin + +dotnet clean +rm -rf /tmp/build + +dotnet build +./ci/nuget-pack.sh "0.0.0" "0.0.0" "0.0.0" "0.0.0" "/tmp/build/bin/net-ssa/package" + +popd \ No newline at end of file diff --git a/ci/docker/Dockerfile b/ci/docker/Dockerfile index 6680f8b..5e33400 100644 --- a/ci/docker/Dockerfile +++ b/ci/docker/Dockerfile @@ -3,6 +3,6 @@ FROM ${BASE_IMAGE} COPY --chown=ubuntu:mygroup . ${NET_SSA_SRC_DIR} -RUN dotnet build && \ +RUN ./ci/build.sh && \ dotnet test --verbosity normal && \ lit ./integration-test -vv diff --git a/ci/docker/Dockerfile.base b/ci/docker/Dockerfile.base index 99c75c1..912c816 100644 --- a/ci/docker/Dockerfile.base +++ b/ci/docker/Dockerfile.base @@ -22,7 +22,9 @@ COPY --chown=ubuntu:mygroup ./ci/ ${NET_SSA_SRC_DIR}/ci RUN sudo ${NET_SSA_SRC_DIR}/ci/install-souffle.sh && \ sudo ${NET_SSA_SRC_DIR}/ci/install-lit.sh && \ sudo ${NET_SSA_SRC_DIR}/ci/install-llvm.sh && \ - sudo ${NET_SSA_SRC_DIR}/ci/install-mono.sh + sudo ${NET_SSA_SRC_DIR}/ci/install-mono.sh && \ + dotnet tool install -g dotnet-repl +ENV PATH="$PATH:/home/ubuntu/.dotnet/tools" WORKDIR ${NET_SSA_SRC_DIR} diff --git a/ci/nuget-pack.sh b/ci/nuget-pack.sh index 6983217..e1373c2 100755 --- a/ci/nuget-pack.sh +++ b/ci/nuget-pack.sh @@ -7,9 +7,10 @@ VERSION=$1 ASSEMBLY_VERSION=$2 INFORMATIONAL_VERSION=$3 PACKAGE_VERSION=$4 +OUTPUT_DIR=$5 pushd $CURRENT_DIR/.. -dotnet pack net-ssa.sln -o:build/bin/net-ssa/package --include-symbols --include-source /p:Version=$VERSION /p:AssemblyVersion=$ASSEMBLY_VERSION /p:InformationalVersion=$INFORMATIONAL_VERSION /p:PackageVersion=$PACKAGE_VERSION +dotnet pack net-ssa.sln -o:$OUTPUT_DIR --include-symbols --include-source /p:Version=$VERSION /p:AssemblyVersion=$ASSEMBLY_VERSION /p:InformationalVersion=$INFORMATIONAL_VERSION /p:PackageVersion=$PACKAGE_VERSION popd \ No newline at end of file diff --git a/integration-test/lit.cfg.py b/integration-test/lit.cfg.py index 3fdae15..fc9f448 100644 --- a/integration-test/lit.cfg.py +++ b/integration-test/lit.cfg.py @@ -51,6 +51,7 @@ def _get_llvm_bindir(): config.test_build_root = os.path.join(config.test_source_root, "output") if not os.path.exists(config.test_build_root): os.mkdir(config.test_build_root) +config.test_exec_root = config.test_build_root config.substitutions.append(('%mono', config.mono_bin)) config.substitutions.append(('%mcs', config.mcs_bin)) @@ -60,7 +61,7 @@ def _get_llvm_bindir(): config.substitutions.append(('%FileCheck', os.path.join(config.llvm_bin_dir, "FileCheck"))) config.substitutions.append(('%test-resources-dir', os.path.join(config.my_src_root, "test-resources"))) -env_vars = {'DOTNET_ROOT'} +env_vars = {'DOTNET_ROOT', 'HOME'} for e in env_vars: if e in os.environ: config.environment[e] = os.environ[e] diff --git a/integration-test/net-ssa-lib/live-variables/test0/program.cs.exclude b/integration-test/net-ssa-lib/live-variables/test0/program.cs.exclude new file mode 100644 index 0000000..a343c2c --- /dev/null +++ b/integration-test/net-ssa-lib/live-variables/test0/program.cs.exclude @@ -0,0 +1,10 @@ +using System; +class HelloWorld +{ + static void Main() + { + for (int i=0; i < 10; i++){ + Console.WriteLine(i); + } + } +} \ No newline at end of file diff --git a/integration-test/net-ssa-lib/live-variables/test0/test.cs b/integration-test/net-ssa-lib/live-variables/test0/test.cs new file mode 100644 index 0000000..f7a2c36 --- /dev/null +++ b/integration-test/net-ssa-lib/live-variables/test0/test.cs @@ -0,0 +1,88 @@ +// RUN: %mcs -out:%T/Test.dll %S/program.cs.exclude +// RUN: (export BINARY_PATH=%T/Test.dll && dotnet repl --output-path %t.trx --run %s --exit-after-run) + +#i "nuget:/tmp/build/bin/net-ssa/package" +#r "nuget: net-ssa-lib, 0.0.0" +#r "nuget: Mono.Cecil, 0.11.3" +#r "nuget: NUnit, 3.12.0" + +using System; +using System.Collections.Generic; + +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; + +using NetSsa.Analyses; +using NetSsa.Instructions; + +using NUnit.Framework; +using System.Linq; + +public static String GetRegisters(IList registers, BitArray liveVariables){ + String result = ""; + for (int i=0; i < liveVariables.Length; i++){ + if (liveVariables[i]){ + result += registers[i].Name + " "; + } + } + return result.Trim(); +} + +AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(Environment.GetEnvironmentVariable("BINARY_PATH")); + +var method = assembly.MainModule.GetType("HelloWorld").GetMethods().First(); +Mono.Cecil.Cil.MethodBody body = method.Body; +IRBody irBody = Unstacker.Compute(body); +ControlFlowGraph cfg = new ControlFlowGraph(irBody); +LiveVariableAnalysis lva = new LiveVariableAnalysis(cfg); +lva.Flow(); + +/* +L_0000: label +L_0001: nop +L_0002: s0 = ldc.i4.0 +L_0003: l0 = stloc.0 [s0] -- s0 +L_0004: br L_000c +L_0005: label +L_0006: s0 = ldloc.0 [l0] +L_0007: call System.Void System.Console::WriteLine(System.Int32) [s0] -- s0 +L_0008: s0 = ldloc.0 [l0] +L_0009: s1 = ldc.i4.1 ---- [s0] +L_000a: s0 = add [s0, s1] --- [s0, s1] +L_000b: l0 = stloc.0 [s0] --- [s0] +L_000c: label +L_000d: s0 = ldloc.0 [l0] +L_000e: s1 = ldc.i4.s 10 ---- [s0] +L_000f: blt L_0005 [s0, s1] -- s0 s1 +L_0010: label +L_0011: ret +*/ + +Assert.AreEqual(irBody.Instructions.Count, 0x12); + +Dictionary results = new Dictionary() +{ + { 0x0, "" }, + { 0x1, "" }, + { 0x2, "" }, + { 0x3, "s0" }, + { 0x4, "" }, + { 0x5, "" }, + { 0x6, "" }, + { 0x7, "s0" }, + { 0x8, "" }, + { 0x9, "s0" }, + { 0xa, "s0 s1" }, + { 0xb, "s0" }, + { 0xc, "" }, + { 0xd, "" }, + { 0xe, "s0" }, + { 0xf, "s0 s1" }, + { 0x10, "" }, + { 0x11, "" }, +}; + +for (int i=0; i < 0x12; i++){ + Assert.AreEqual(results[i], GetRegisters(irBody.Registers, lva.IN[irBody.Instructions.ElementAt(i)]), i.ToString("x")); +} diff --git a/net-ssa-lib/analyses/DataFlowAnalysis.cs b/net-ssa-lib/analyses/DataFlowAnalysis.cs new file mode 100644 index 0000000..7015dca --- /dev/null +++ b/net-ssa-lib/analyses/DataFlowAnalysis.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using NetSsa.Instructions; +using System.Linq; + +namespace NetSsa.Analyses +{ + public abstract class BackwardsDataFlowAnalysis + { + public BackwardsDataFlowAnalysis(ControlFlowGraph cfg){ + this.cfg = cfg; + this.irBody = cfg.IRBody; + } + + protected ControlFlowGraph cfg; + protected IRBody irBody; + + protected abstract Data Meet(Data a, Data b); // This is expected to create a copy of the input data + protected virtual Data MeetSuccessors(TacInstruction leader){ + IList successors = cfg.BasicBlockSuccessors(leader); + Data result = IN[successors[0]]; + for (int i = 1; i < successors.Count; i++){ + result = Meet(result, IN[successors[i]]); + } + return result; + } + + protected abstract Data Gen(TacInstruction leader); + protected abstract Data Kill(TacInstruction leader); + protected abstract bool TransferInstruction(TacInstruction instruction, Data incomingData); + + protected virtual void Transfer(TacInstruction leader, Data incomingData, ref bool changed){ + IList instructions = cfg.BasicBlockInstructions(leader).ToList(); + + bool currentChanged = TransferInstruction(instructions[instructions.Count-1], incomingData); + for (int i=instructions.Count-2; i >= 0; i--){ + currentChanged = currentChanged || TransferInstruction(instructions[i], IN[instructions[i+1]]); + } + + changed = currentChanged; + } + + protected abstract Data InitBasicBlock(TacInstruction leader); + + public IDictionary IN = new Dictionary(); + public IDictionary OUT = new Dictionary(); + + protected virtual IEnumerable GetExitBasicBlocks(){ + foreach (TacInstruction leader in cfg.Leaders()){ + if (cfg.BasicBlockSuccessors(leader).Count == 0){ + yield return leader; + } + } + } + + protected void InitializeBasicBlocks(){ + foreach (TacInstruction leader in cfg.Leaders()){ + foreach (TacInstruction instruction in cfg.BasicBlockInstructions(leader)){ + IN[instruction] = InitBasicBlock(leader); + } + } + } + + public void Flow(){ + InitializeBasicBlocks(); + bool inChanged; + do { + inChanged = false; + foreach (TacInstruction leader in cfg.Leaders().Except(GetExitBasicBlocks())){ + OUT[leader] = MeetSuccessors(leader); + Transfer(leader, OUT[leader], ref inChanged); + } + } while (inChanged); + } + } + +} \ No newline at end of file diff --git a/net-ssa-lib/analyses/LiveVariableAnalysis.cs b/net-ssa-lib/analyses/LiveVariableAnalysis.cs new file mode 100644 index 0000000..2878819 --- /dev/null +++ b/net-ssa-lib/analyses/LiveVariableAnalysis.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Collections; +using System; +using NetSsa.Instructions; +using System.Linq; + +namespace NetSsa.Analyses{ + public class LiveVariableAnalysis : BackwardsDataFlowAnalysis { + public LiveVariableAnalysis(ControlFlowGraph cfg) : base(cfg) {} + + private int NumberOfRegisters(){ + return irBody.Registers.Count; + } + + // TO-DO: Find faster implementation + private int RegisterIndex(Register register){ + return irBody.Registers.IndexOf(register); + } + + // TO-DO: Find faster implementation + private bool AreEqual(BitArray a, BitArray b) { + if (a.Length != b.Length){ + return false; + } + + for (int i=0; i < a.Length; i++){ + if (a[i] != b[i]){ + return false; + } + } + + return true; + } + + protected override BitArray Meet(BitArray a, BitArray b) { + return a.Or(b); + } + + protected override BitArray Gen(TacInstruction instruction) { + BitArray result = new BitArray(NumberOfRegisters()); + foreach (int index in instruction.Operands.OfType().Select(r => RegisterIndex(r))){ + result.Set(index, true); + } + + return result; + } + + protected override BitArray Kill(TacInstruction instruction) { + BitArray result = new BitArray(NumberOfRegisters()); + + if (instruction.Result is Register){ + result.Set(RegisterIndex((Register)instruction.Result), true); + } + + return result; + } + + protected override bool TransferInstruction(TacInstruction instruction, BitArray incomingData){ + BitArray gen = Gen(instruction); + BitArray kill = Kill(instruction); + + // The difference of two sets S T is computed by + // complementing the bit vector of T, and then taking the logical AND of that + // complement, with the bit vector for S. + + BitArray newValue = gen.Or(kill.Not().And(incomingData)); + + BitArray old = IN[instruction]; + if (AreEqual(old, newValue)){ + return false; + } else { + IN[instruction] = newValue; + return true; + } + } + + protected override BitArray InitBasicBlock(TacInstruction leader){ + return new BitArray(NumberOfRegisters()); + } + } +} \ No newline at end of file