Initial support for the System-V ABI

This commit is contained in:
gdk 2019-07-26 22:39:37 -03:00 committed by gdkchan
parent 40b8a2a750
commit ceb5c055a0
12 changed files with 452 additions and 81 deletions

View file

@ -0,0 +1,8 @@
namespace ARMeilleure.CodeGen.X86
{
enum CallConvName
{
SystemV,
Windows
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
namespace ARMeilleure.CodeGen.X86
{
@ -18,23 +19,45 @@ namespace ARMeilleure.CodeGen.X86
public static int GetIntCallerSavedRegisters()
{
return (1 << (int)X86Register.Rax) |
(1 << (int)X86Register.Rdx) |
(1 << (int)X86Register.Rcx) |
(1 << (int)X86Register.R8) |
(1 << (int)X86Register.R9) |
(1 << (int)X86Register.R10) |
(1 << (int)X86Register.R11);
if (GetCurrentCallConv() == CallConvName.Windows)
{
return (1 << (int)X86Register.Rax) |
(1 << (int)X86Register.Rcx) |
(1 << (int)X86Register.Rdx) |
(1 << (int)X86Register.R8) |
(1 << (int)X86Register.R9) |
(1 << (int)X86Register.R10) |
(1 << (int)X86Register.R11);
}
else /* if (GetCurrentCallConv() == CallConvName.SystemV) */
{
return (1 << (int)X86Register.Rax) |
(1 << (int)X86Register.Rcx) |
(1 << (int)X86Register.Rdx) |
(1 << (int)X86Register.Rsi) |
(1 << (int)X86Register.Rdi) |
(1 << (int)X86Register.R8) |
(1 << (int)X86Register.R9) |
(1 << (int)X86Register.R10) |
(1 << (int)X86Register.R11);
}
}
public static int GetVecCallerSavedRegisters()
{
return (1 << (int)X86Register.Xmm0) |
(1 << (int)X86Register.Xmm1) |
(1 << (int)X86Register.Xmm2) |
(1 << (int)X86Register.Xmm3) |
(1 << (int)X86Register.Xmm4) |
(1 << (int)X86Register.Xmm5);
if (GetCurrentCallConv() == CallConvName.Windows)
{
return (1 << (int)X86Register.Xmm0) |
(1 << (int)X86Register.Xmm1) |
(1 << (int)X86Register.Xmm2) |
(1 << (int)X86Register.Xmm3) |
(1 << (int)X86Register.Xmm4) |
(1 << (int)X86Register.Xmm5);
}
else /* if (GetCurrentCallConv() == CallConvName.SystemV) */
{
return RegistersMask;
}
}
public static int GetIntCalleeSavedRegisters()
@ -52,14 +75,39 @@ namespace ARMeilleure.CodeGen.X86
return 4;
}
public static int GetIntArgumentsOnRegsCount()
{
return 6;
}
public static int GetVecArgumentsOnRegsCount()
{
return 8;
}
public static X86Register GetIntArgumentRegister(int index)
{
switch (index)
if (GetCurrentCallConv() == CallConvName.Windows)
{
case 0: return X86Register.Rcx;
case 1: return X86Register.Rdx;
case 2: return X86Register.R8;
case 3: return X86Register.R9;
switch (index)
{
case 0: return X86Register.Rcx;
case 1: return X86Register.Rdx;
case 2: return X86Register.R8;
case 3: return X86Register.R9;
}
}
else /* if (GetCurrentCallConv() == CallConvName.SystemV) */
{
switch (index)
{
case 0: return X86Register.Rdi;
case 1: return X86Register.Rsi;
case 2: return X86Register.Rdx;
case 3: return X86Register.Rcx;
case 4: return X86Register.R8;
case 5: return X86Register.R9;
}
}
throw new ArgumentOutOfRangeException(nameof(index));
@ -67,12 +115,20 @@ namespace ARMeilleure.CodeGen.X86
public static X86Register GetVecArgumentRegister(int index)
{
switch (index)
int count;
if (GetCurrentCallConv() == CallConvName.Windows)
{
case 0: return X86Register.Xmm0;
case 1: return X86Register.Xmm1;
case 2: return X86Register.Xmm2;
case 3: return X86Register.Xmm3;
count = 4;
}
else /* if (GetCurrentCallConv() == CallConvName.SystemV) */
{
count = 8;
}
if ((uint)index < count)
{
return X86Register.Xmm0 + index;
}
throw new ArgumentOutOfRangeException(nameof(index));
@ -83,9 +139,21 @@ namespace ARMeilleure.CodeGen.X86
return X86Register.Rax;
}
public static X86Register GetIntReturnRegisterHigh()
{
return X86Register.Rdx;
}
public static X86Register GetVecReturnRegister()
{
return X86Register.Xmm0;
}
public static CallConvName GetCurrentCallConv()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? CallConvName.Windows
: CallConvName.SystemV;
}
}
}

View file

@ -116,7 +116,7 @@ namespace ARMeilleure.CodeGen.X86
int frameSize = calleeSaveRegionSize + allocResult.SpillRegionSize;
int callArgsAndFrameSize = frameSize + argsCount * 8;
int callArgsAndFrameSize = frameSize + argsCount * 16; //FIXME * 16 => calc
//Ensure that the Stack Pointer will be aligned to 16 bytes.
callArgsAndFrameSize = (callArgsAndFrameSize + 0xf) & ~0xf;

View file

@ -117,7 +117,7 @@ namespace ARMeilleure.CodeGen.X86
Logger.StartPass(PassName.RegisterAllocation);
FastLinearScan regAlloc = new FastLinearScan();
LinearScan regAlloc = new LinearScan();
RegisterMasks regMasks = new RegisterMasks(
CallingConvention.GetIntAvailableRegisters(),
@ -1607,7 +1607,7 @@ namespace ARMeilleure.CodeGen.X86
size = (size + pageMask) & ~pageMask;
Operand rsp = Register(X86Register.Rsp);
Operand temp = Register(CallingConvention.GetIntReturnRegister());
Operand temp = Register(CallingConvention.GetIntReturnRegister());
for (int offset = PageSize; offset < size; offset += PageSize)
{

View file

@ -38,7 +38,9 @@ namespace ARMeilleure.CodeGen.X86
ControlFlowGraph cfg = context.GetControlFlowGraph();
GetFeatureInfo getFeatureInfo = Compiler.Compile<GetFeatureInfo>(cfg, OperandType.I64);
OperandType[] argTypes = new OperandType[0];
GetFeatureInfo getFeatureInfo = Compiler.Compile<GetFeatureInfo>(cfg, argTypes, OperandType.I64);
_featureInfo = getFeatureInfo();
}

View file

@ -16,6 +16,8 @@ namespace ARMeilleure.CodeGen.X86
{
maxCallArgs = -1;
CallConvName callConv = CallingConvention.GetCurrentCallConv();
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
foreach (BasicBlock block in cctx.Cfg.Blocks)
@ -58,7 +60,14 @@ namespace ARMeilleure.CodeGen.X86
// Copy values to registers expected by the function
// being called, as mandated by the ABI.
node = HandleCallWindowsAbi(stackAlloc, node, operation);
if (callConv == CallConvName.Windows)
{
node = HandleCallWindowsAbi(stackAlloc, node, operation);
}
else /* if (callConv == CallConvName.SystemV) */
{
node = HandleCallSystemVAbi(node, operation);
}
break;
case Instruction.ConvertToFPUI:
@ -66,7 +75,14 @@ namespace ARMeilleure.CodeGen.X86
break;
case Instruction.LoadArgument:
HandleLoadArgumentWindowsAbi(cctx, node, preservedArgs, operation);
if (callConv == CallConvName.Windows)
{
HandleLoadArgumentWindowsAbi(cctx, node, preservedArgs, operation);
}
else
{
HandleLoadArgumentSystemVAbi(cctx, node, preservedArgs, operation);
}
break;
case Instruction.Negate:
@ -77,7 +93,14 @@ namespace ARMeilleure.CodeGen.X86
break;
case Instruction.Return:
HandleReturnWindowsAbi(cctx, node, preservedArgs, operation);
if (callConv == CallConvName.Windows)
{
HandleReturnWindowsAbi(cctx, node, preservedArgs, operation);
}
else /* if (callConv == CallConvName.SystemV) */
{
HandleReturnSystemVAbi(node, operation);
}
break;
case Instruction.VectorInsert8:
@ -612,9 +635,9 @@ namespace ARMeilleure.CodeGen.X86
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
}
Operation srcCopyOp = new Operation(Instruction.Copy, argReg, source);
Operation copyOp = new Operation(Instruction.Copy, argReg, source);
HandleConstantCopy(nodes.AddBefore(node, srcCopyOp), srcCopyOp);
HandleConstantCopy(nodes.AddBefore(node, copyOp), copyOp);
operation.SetSource(index + 1, argReg);
}
@ -656,20 +679,120 @@ namespace ARMeilleure.CodeGen.X86
{
RegisterType regType = dest.Type.ToRegisterType();
Operand retReg;
Operand retReg = regType == RegisterType.Integer
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
if (regType == RegisterType.Integer)
{
retReg = Gpr(CallingConvention.GetIntReturnRegister(), dest.Type);
}
else /* if (regType == RegisterType.Vector) */
{
retReg = Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
}
Operation copyOp = new Operation(Instruction.Copy, dest, retReg);
Operation destCopyOp = new Operation(Instruction.Copy, dest, retReg);
node = nodes.AddAfter(node, copyOp);
node = nodes.AddAfter(node, destCopyOp);
operation.Dest = retReg;
}
}
return node;
}
private static LLNode HandleCallSystemVAbi(LLNode node, Operation operation)
{
Operand dest = operation.Dest;
LinkedList<Node> nodes = node.List;
// Handle arguments passed on registers.
int argsCount = operation.SourcesCount - 1;
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
int intCount = 0;
int vecCount = 0;
int stackOffset = 0;
for (int index = 0; index < argsCount; index++)
{
Operand source = operation.GetSource(index + 1);
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < intMax;
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < intMax;
}
else
{
passOnReg = vecCount < vecMax;
}
if (source.Type == OperandType.V128 && passOnReg)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
nodes.AddBefore(node, new Operation(Instruction.VectorExtract, argReg, source, Const(0)));
nodes.AddBefore(node, new Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
operation.SetSource(index + 1, Undef());
continue;
}
if (passOnReg)
{
Operand argReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
Operation copyOp = new Operation(Instruction.Copy, argReg, source);
HandleConstantCopy(nodes.AddBefore(node, copyOp), copyOp);
operation.SetSource(index + 1, argReg);
}
else
{
Operand offset = new Operand(stackOffset);
Operation spillOp = new Operation(Instruction.SpillArg, null, offset, source);
HandleConstantCopy(nodes.AddBefore(node, spillOp), spillOp);
stackOffset += source.Type.GetSizeInBytes();
operation.SetSource(index + 1, Undef());
}
}
if (dest != null)
{
if (dest.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
node = nodes.AddAfter(node, new Operation(Instruction.VectorCreateScalar, dest, retLReg));
node = nodes.AddAfter(node, new Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
operation.Dest = null;
}
else
{
RegisterType regType = dest.Type.ToRegisterType();
Operand retReg = regType == RegisterType.Integer
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
Operation copyOp = new Operation(Instruction.Copy, dest, retReg);
node = nodes.AddAfter(node, copyOp);
operation.Dest = retReg;
}
@ -698,44 +821,144 @@ namespace ARMeilleure.CodeGen.X86
if (preservedArgs[index] == null)
{
Operand preservedArg;
Operand argReg;
Operand argReg, pArg;
if (dest.Type.IsInteger())
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
preservedArg = Local(dest.Type);
pArg = Local(dest.Type);
}
else if (dest.Type == OperandType.V128)
{
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
preservedArg = Local(OperandType.I64);
pArg = Local(OperandType.I64);
}
else /* if (regType == RegisterType.Vector) */
else
{
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
preservedArg = Local(dest.Type);
pArg = Local(dest.Type);
}
Operation copyOp = new Operation(Instruction.Copy, preservedArg, argReg);
Operation copyOp = new Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = preservedArg;
preservedArgs[index] = pArg;
}
Operation loadArgOp = new Operation(dest.Type == OperandType.V128
Operation argCopyOp = new Operation(dest.Type == OperandType.V128
? Instruction.Load
: Instruction.Copy, dest, preservedArgs[index]);
node.List.AddBefore(node, loadArgOp);
node.List.AddBefore(node, argCopyOp);
Delete(node, operation);
}
else
{
// TODO: Pass on stack.
}
}
private static void HandleLoadArgumentSystemVAbi(
CompilerContext cctx,
LLNode node,
Operand[] preservedArgs,
Operation operation)
{
Operand source = operation.GetSource(0);
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
int index = source.AsInt32();
int intCount = 0;
int vecCount = 0;
for (int cIndex = 0; cIndex < index; cIndex++)
{
OperandType argType = cctx.FuncArgTypes[cIndex];
if (argType.IsInteger())
{
intCount++;
}
else if (argType == OperandType.V128)
{
intCount += 2;
}
else
{
vecCount++;
}
}
bool passOnReg;
if (source.Type.IsInteger())
{
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
}
else if (source.Type == OperandType.V128)
{
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
}
else
{
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
}
if (passOnReg)
{
Operand dest = operation.Dest;
if (preservedArgs[index] == null)
{
if (dest.Type == OperandType.V128)
{
// V128 is a struct, we pass each half on a GPR if possible.
Operand pArg = Local(OperandType.V128);
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
Operation copyL = new Operation(Instruction.VectorCreateScalar, pArg, argLReg);
Operation copyH = new Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
cctx.Cfg.Entry.Operations.AddFirst(copyH);
cctx.Cfg.Entry.Operations.AddFirst(copyL);
preservedArgs[index] = pArg;
}
else
{
Operand pArg = Local(dest.Type);
Operand argReg = dest.Type.IsInteger()
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
Operation copyOp = new Operation(Instruction.Copy, pArg, argReg);
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
preservedArgs[index] = pArg;
}
}
Operation argCopyOp = new Operation(Instruction.Copy, dest, preservedArgs[index]);
node.List.AddBefore(node, argCopyOp);
Delete(node, operation);
}
else
{
// TODO: Pass on stack.
}
}
private static void HandleReturnWindowsAbi(
@ -774,7 +997,7 @@ namespace ARMeilleure.CodeGen.X86
retReg = preservedArgs[0];
}
else /* if (regType == RegisterType.Vector) */
else
{
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
}
@ -793,6 +1016,35 @@ namespace ARMeilleure.CodeGen.X86
}
}
private static void HandleReturnSystemVAbi(LLNode node, Operation operation)
{
if (operation.SourcesCount == 0)
{
return;
}
Operand source = operation.GetSource(0);
if (source.Type == OperandType.V128)
{
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
node.List.AddBefore(node, new Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
node.List.AddBefore(node, new Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
}
else
{
Operand retReg = source.Type.IsInteger()
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
Operation retCopyOp = new Operation(Instruction.Copy, retReg, source);
node.List.AddBefore(node, retCopyOp);
}
}
private static Operand AddXmmCopy(LLNode node, Operand source)
{
Operand temp = Local(source.Type);

View file

@ -59,7 +59,14 @@ namespace ARMeilleure.Memory
ControlFlowGraph cfg = context.GetControlFlowGraph();
_compareExchange128 = Compiler.Compile<CompareExchange128>(cfg, OperandType.V128);
OperandType[] argTypes = new OperandType[]
{
OperandType.I64,
OperandType.V128,
OperandType.V128
};
_compareExchange128 = Compiler.Compile<CompareExchange128>(cfg, argTypes, OperandType.V128);
}
}
}

View file

@ -9,9 +9,12 @@ namespace ARMeilleure.Translation
{
static class Compiler
{
public static T Compile<T>(ControlFlowGraph cfg, OperandType funcReturnType)
public static T Compile<T>(
ControlFlowGraph cfg,
OperandType[] funcArgTypes,
OperandType funcReturnType)
{
CompilerContext cctx = GetCompilerContext(cfg, funcReturnType);
CompilerContext cctx = GetCompilerContext(cfg, funcArgTypes, funcReturnType);
CompiledFunction func = CodeGenerator.Generate(cctx);
@ -20,7 +23,10 @@ namespace ARMeilleure.Translation
return Marshal.GetDelegateForFunctionPointer<T>(codePtr);
}
private static CompilerContext GetCompilerContext(ControlFlowGraph cfg, OperandType funcReturnType)
private static CompilerContext GetCompilerContext(
ControlFlowGraph cfg,
OperandType[] funcArgTypes,
OperandType funcReturnType)
{
Logger.StartPass(PassName.Dominance);
@ -35,7 +41,7 @@ namespace ARMeilleure.Translation
Logger.EndPass(PassName.SsaConstruction, cfg);
return new CompilerContext(cfg, funcReturnType);
return new CompilerContext(cfg, funcArgTypes, funcReturnType);
}
}
}

View file

@ -6,11 +6,16 @@ namespace ARMeilleure.Translation
{
public ControlFlowGraph Cfg { get; }
public OperandType FuncReturnType { get; }
public OperandType[] FuncArgTypes { get; }
public OperandType FuncReturnType { get; }
public CompilerContext(ControlFlowGraph cfg, OperandType funcReturnType)
public CompilerContext(
ControlFlowGraph cfg,
OperandType[] funcArgTypes,
OperandType funcReturnType)
{
Cfg = cfg;
FuncArgTypes = funcArgTypes;
FuncReturnType = funcReturnType;
}
}

View file

@ -2,6 +2,7 @@ using ARMeilleure.CodeGen;
using ARMeilleure.Memory;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
namespace ARMeilleure.Translation
@ -25,10 +26,13 @@ namespace ARMeilleure.Translation
{
_basePointer = MemoryManagement.Allocate(CacheSize);
JitUnwindWindows.InstallFunctionTableHandler(_basePointer, CacheSize);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
JitUnwindWindows.InstallFunctionTableHandler(_basePointer, CacheSize);
//The first page is used for the table based SEH structs.
_offset = PageSize;
// The first page is used for the table based SEH structs.
_offset = PageSize;
}
_cacheEntries = new List<JitCacheEntry>();
}
@ -41,26 +45,44 @@ namespace ARMeilleure.Translation
IntPtr funcPtr = _basePointer + funcOffset;
unsafe
{
fixed (byte* codePtr = code)
{
byte* dest = (byte*)funcPtr;
Marshal.Copy(code, 0, funcPtr, code.Length);
long size = (long)code.Length;
Buffer.MemoryCopy(codePtr, dest, size, size);
}
}
//TODO: W^X.
MemoryManagement.Reprotect(funcPtr, (ulong)code.Length, MemoryProtection.ReadWriteExecute);
ReprotectRange(funcOffset, code.Length);
Add(new JitCacheEntry(funcOffset, code.Length, func.UnwindInfo));
return funcPtr;
}
private static void ReprotectRange(int offset, int size)
{
// Map pages that are already full as RX.
// Map pages that are not full yet as RWX.
// On unix, the address and size must be page aligned.
int endOffs = offset + size;
int pageStart = offset & ~PageMask;
int pageEnd = endOffs & ~PageMask;
int fullPagesSize = pageEnd - pageStart;
if (fullPagesSize != 0)
{
IntPtr funcPtr = _basePointer + pageStart;
MemoryManagement.Reprotect(funcPtr, (ulong)fullPagesSize, MemoryProtection.ReadAndExecute);
}
int remaining = endOffs - pageEnd;
if (remaining != 0)
{
IntPtr funcPtr = _basePointer + pageEnd;
MemoryManagement.Reprotect(funcPtr, (ulong)remaining, MemoryProtection.ReadWriteExecute);
}
}
private static int Allocate(int codeSize)
{
codeSize = checked(codeSize + (CodeAlignment - 1)) & ~(CodeAlignment - 1);
@ -79,7 +101,7 @@ namespace ARMeilleure.Translation
private static void Add(JitCacheEntry entry)
{
//TODO: Use concurrent collection.
// TODO: Use concurrent collection.
lock (_cacheEntries)
{
_cacheEntries.Add(entry);

View file

@ -1,5 +1,4 @@
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Memory;
using System;
using System.Runtime.InteropServices;

View file

@ -93,7 +93,9 @@ namespace ARMeilleure.Translation
Logger.EndPass(PassName.RegisterUsage);
GuestFunction func = Compiler.Compile<GuestFunction>(cfg, OperandType.I64);
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
GuestFunction func = Compiler.Compile<GuestFunction>(cfg, argTypes, OperandType.I64);
return new TranslatedFunction(func);
}