IFPSTools.NET/LibIFPSCC/CGen/CGen.cs
zc e16c00799d ifpscc: initial commit
libifpscc: initial commit
readme: document ifpscc/libifpscc
license: add credits for ifpscc/libifpscc (derived from code also MIT licensed)
libifps: make additional fields/types public for libifpscc
libifps: fix field documentation for some opcodes
libifps: fix loading functions that are not exported
libifps: allow saving a nonexistant primitive type if the same primitive type was added already
libifps: fix parsing Extended constants
libifps: fix ushort/short being mapped to the wrong types in one table
csproj: set Prefer32Bit=false for release builds
2023-03-28 17:24:19 +01:00

924 lines
40 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Xml.Linq;
using IFPSLib;
using Emit = IFPSLib.Emit;
using Types = IFPSLib.Types;
namespace CodeGeneration {
internal static class OperandEquator
{
internal static bool Equals(Emit.IVariable lhs, Emit.IVariable rhs)
{
if (lhs == rhs) return true;
if (lhs == null || rhs == null) return false;
if (lhs.VarType != rhs.VarType) return false;
return lhs.Index == rhs.Index;
}
internal static bool Equals(Emit.Operand lhs, Emit.Operand rhs)
{
if (lhs == rhs) return true;
if (lhs == null || rhs == null) return false;
if (lhs.Type != rhs.Type) return false;
switch (lhs.Type)
{
case Emit.BytecodeOperandType.Immediate:
return lhs.Immediate == rhs.Immediate;
case Emit.BytecodeOperandType.Variable:
return Equals(lhs.Variable, rhs.Variable);
case Emit.BytecodeOperandType.IndexedImmediate:
return Equals(lhs.IndexedVariable, rhs.IndexedVariable) && lhs.IndexImmediate == rhs.IndexImmediate;
case Emit.BytecodeOperandType.IndexedVariable:
return Equals(lhs.IndexedVariable, rhs.IndexedVariable) && Equals(lhs.IndexVariable, rhs.IndexVariable);
default:
return false;
}
}
}
public class CGenState {
public interface IFunctionGen
{
Emit.ScriptFunction Function { get; }
List<Emit.Instruction> Labels { get; }
Dictionary<int, int> LabelToIndex { get; }
Dictionary<int, int> IndexToLabel { get; }
int LocalsCount { get; }
Emit.Operand PushType(Types.IType type);
Emit.Operand PushVar(Emit.Operand op);
Emit.Operand Push(Emit.Operand op);
Emit.Operand AddLocal();
void Pop();
void PopForRevert();
}
private sealed class FunctionGen : IFunctionGen
{
public Emit.ScriptFunction Function { get; }
public List<Emit.Instruction> Labels { get; } = new List<Emit.Instruction>();
public Dictionary<int, int> LabelToIndex { get; } = new Dictionary<int, int>();
public Dictionary<int, int> IndexToLabel { get; } = new Dictionary<int, int>();
public int LocalsCount { get; private set; }
private Emit.Operand PushCore(Emit.Instruction insn)
{
Function.Instructions.Add(insn);
return AddLocal();
}
public Emit.Operand PushType(Types.IType type) => PushCore(Emit.Instruction.Create(Emit.OpCodes.PushType, type));
public Emit.Operand PushVar(Emit.Operand op) => PushCore(Emit.Instruction.Create(Emit.OpCodes.PushVar, op));
public Emit.Operand Push(Emit.Operand op) => PushCore(Emit.Instruction.Create(Emit.OpCodes.Push, op));
public Emit.Operand AddLocal()
{
var ret = Emit.Operand.Create(Emit.LocalVariable.Create(LocalsCount));
LocalsCount++;
return ret;
}
public void Pop()
{
Function.Instructions.Add(Emit.Instruction.Create(Emit.OpCodes.Pop));
LocalsCount--;
}
public void PopForRevert()
{
LocalsCount--;
}
public FunctionGen(string name, ABT.StorageClass scs)
{
Function = new Emit.ScriptFunction();
Function.Name = name;
Function.Exported = scs != ABT.StorageClass.STATIC;
// caller must deal with arguments.
}
}
public Script Script { get; } = new Script();
private IFunctionGen m_Constructor = null;
public IFunctionGen FunctionState { get; private set; } = null;
public IList<Emit.Instruction> CurrInsns => FunctionState?.Function.Instructions;
private Dictionary<string, IFunctionGen> m_Functions = new Dictionary<string, IFunctionGen>();
private Dictionary<string, Emit.ExternalFunction> m_FunctionsExternal = new Dictionary<string, Emit.ExternalFunction>();
private Types.IType m_TypePointer = null;
private Types.IType m_TypeUByte = null;
private Types.IType m_TypeU16 = null;
private Types.IType m_TypeU32 = null;
private Types.IType m_TypeIUnknown = null;
private Types.IType m_TypeIDispatch = null;
// Additional runtime functions/types required.
/// <summary>
/// Imports a stubbed function in ntdll.dll (just returns);
/// This can be used with fastcall calling convention to get the underlying pointer of a value passed by reference
/// </summary>
private Emit.ExternalFunction m_CastPointerRef = null;
/// <summary>
/// Imports ntdll!RtlMoveMemory, copying between two pointers passed as by-reference variables
/// </summary>
private Emit.ExternalFunction m_RtlMoveMemoryRef = null;
/// <summary>
/// Imports ntdll!RtlMoveMemory, copying between two pointers passed as values.
/// </summary>
private Emit.ExternalFunction m_RtlMoveMemoryVal = null;
/// <summary>
/// 1-length array of pointer, used to work around the inability to create a raw Pointer in the runtime (causes null deref if you try)
/// </summary>
private Types.IType m_TypeArrayOfPointer = null;
/// <summary>
/// <code>void CreateValidPointer(pointer_as_size_t pPtrValue, pointer_as_size_t pPtrType, ref ArrayOfPointer outPtr);</code>
/// Creates a valid pointer in *outPtr, setting the pointer value to ptrValue and the pointer type to pPtrType.pType
/// </summary>
private Emit.ScriptFunction m_CreateValidPointer = null;
/// <summary>
/// <code>void CastRefPointer(ref anytype varForType, size_t ptrValue, ref ArrayOfPointer outPtr)</code>
/// Creates a valid pointer in *outPtr, setting the pointer value to ptrValue and the pointer type to ((pointer)varForType)->pType
/// Basically a wrapper for CreateValidPointer(ptrValue, CastPointerRef(ref varForType), ref outPtr)
/// </summary>
private Emit.ScriptFunction m_CastRefPointer = null;
/// <summary>
/// <code>__interface Cast(__interface self, u32 typeIdx)</code>
/// Casts a COM interface to another, typeIdx is used for internal class to internal class cast and not for COM interface casting.
/// </summary>
private Emit.ExternalFunction m_ComInterfaceCast = null;
/// <summary>
/// <code>u16 VarType(__variant self)</code>
/// Gets the type of a Variant. Used for casting interfaces, we need to know if the type is IUnknown or IDispatch because of lack of support in runtime.
/// </summary>
private Emit.ExternalFunction m_VarType = null;
/// <summary>
/// <code>void SetArrayLength(TArray* array, s32 length)</code>
/// Sets the length of an unbounded array at runtime.
/// </summary>
private Emit.ExternalFunction m_SetArrayLength = null;
/// <summary>
/// When initialising pointers, a global ArrayOfPointer is needed.
/// The pointer needs to be stored in the expected stack local.
/// </summary>
private Emit.GlobalVariable m_PointerInitialiser = null;
public Emit.GlobalVariable PointerInitialiser
{
get
{
if (m_PointerInitialiser == null)
{
m_PointerInitialiser = Emit.GlobalVariable.Create(Script.GlobalVariables.Count, TypeArrayOfPointer, "__PointerInitialiser");
Script.GlobalVariables.Add(m_PointerInitialiser);
}
return m_PointerInitialiser;
}
}
public IFunctionGen Constructor {
get
{
if (m_Constructor == null)
{
m_Constructor = new FunctionGen("__ctor", ABT.StorageClass.AUTO);
m_Constructor.Function.Arguments = new List<FunctionArgument>();
Script.Functions.Add(m_Constructor.Function);
Script.EntryPoint = m_Constructor.Function;
var runonce = CreateGlobal(TypeUByte, "__ctor_runonce");
var insns = m_Constructor.Function.Instructions;
var jumploc = Emit.Instruction.Create<byte>(Emit.OpCodes.Assign, runonce, 1);
insns.Add(Emit.Instruction.Create(Emit.OpCodes.JumpZ, runonce, jumploc));
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Ret));
insns.Add(jumploc);
}
return m_Constructor;
}
}
private Types.IType EnsureTypeCreated(ref Types.IType type, Types.PascalTypeCode code)
{
if (type == null)
{
type = new Types.PrimitiveType(code);
Script.Types.Add(type);
}
return type;
}
private Emit.ExternalFunction EnsureDllImportedFunctionCreated(ref Emit.ExternalFunction func, string dllName, string export, Emit.NativeCallingConvention cc, bool hasReturnArgument, string name, IList<FunctionArgument> args)
{
if (func == null)
{
func = new Emit.ExternalFunction();
var dll = new Emit.FDecl.DLL();
dll.DllName = dllName;
dll.ProcedureName = export;
dll.CallingConvention = cc;
func.Declaration = dll;
func.Name = name;
func.ReturnArgument = hasReturnArgument ? Types.UnknownType.Instance : null;
func.Arguments = args;
func.Exported = true;
Script.Functions.Add(func);
}
return func;
}
private Types.IType EnsureTypeCreated<T>(ref Types.IType type)
{
return EnsureTypeCreated(ref type, Types.EnumHelpers.ToIFPSTypeCode(typeof(T)));
}
private Types.IType EnsureArrayTypeCreated(ref Types.IType type, Types.IType elem, int count, string name)
{
if (type == null)
{
type = new Types.StaticArrayType(elem, count);
type.Name = name;
Script.Types.Add(type);
}
return type;
}
public Types.IType TypePointer => EnsureTypeCreated(ref m_TypePointer, Types.PascalTypeCode.Pointer);
public Types.IType TypeUByte => EnsureTypeCreated<byte>(ref m_TypeUByte);
public Types.IType TypeU16 => EnsureTypeCreated<ushort>(ref m_TypeU16);
public Types.IType TypeU32 => EnsureTypeCreated<uint>(ref m_TypeU32);
public Types.IType TypeArrayOfPointer => EnsureArrayTypeCreated(ref m_TypeArrayOfPointer, TypePointer, 1, "__ArrayOfPointer");
public Types.IType TypeIUnknown
{
get
{
if (m_TypeIUnknown == null)
{
m_TypeIUnknown = new Types.ComInterfaceType(GUID_IUNKNOWN) { Name = "IUnknown" };
Script.Types.Add(m_TypeIUnknown);
}
return m_TypeIUnknown;
}
}
public Types.IType TypeIDispatch
{
get
{
if (m_TypeIDispatch == null)
{
m_TypeIDispatch = new Types.ComInterfaceType(GUID_IDISPATCH)
{
Name = "IDISPATCH",
Exported = true
};
Script.Types.Add(m_TypeIDispatch);
}
return m_TypeIDispatch;
}
}
private static FunctionArgument CreateImportedFunctionArgument(FunctionArgumentType type) => new FunctionArgument()
{
ArgumentType = type,
Type = Types.UnknownType.Instance
};
private static readonly IList<FunctionArgument> ARGS_CASTPOINTERREF = new FunctionArgument[] { CreateImportedFunctionArgument(FunctionArgumentType.Out) };
private static readonly IList<FunctionArgument> ARGS_RTLMOVEMEMORYREF = new FunctionArgument[]
{
CreateImportedFunctionArgument(FunctionArgumentType.Out),
CreateImportedFunctionArgument(FunctionArgumentType.Out),
CreateImportedFunctionArgument(FunctionArgumentType.In)
};
private static readonly IList<FunctionArgument> ARGS_RTLMOVEMEMORYVAL = new FunctionArgument[]
{
CreateImportedFunctionArgument(FunctionArgumentType.In),
CreateImportedFunctionArgument(FunctionArgumentType.In),
CreateImportedFunctionArgument(FunctionArgumentType.In)
};
private static readonly IList<FunctionArgument> ARGS_COMINTERFACECAST = new FunctionArgument[]
{
CreateImportedFunctionArgument(FunctionArgumentType.In),
CreateImportedFunctionArgument(FunctionArgumentType.In)
};
private static readonly IList<FunctionArgument> ARGS_VARTYPE = new FunctionArgument[]
{
CreateImportedFunctionArgument(FunctionArgumentType.In),
};
private static readonly IList<FunctionArgument> ARGS_EMPTY = new FunctionArgument[0];
public IFunction CastPointerRef => EnsureDllImportedFunctionCreated(ref m_CastPointerRef, "ntdll.dll", "RtlDebugPrintTimes", Emit.NativeCallingConvention.Register, true, "__CastPointerRef", ARGS_CASTPOINTERREF);
public IFunction RtlMoveMemoryRef => EnsureDllImportedFunctionCreated(ref m_RtlMoveMemoryRef, "ntdll.dll", "RtlMoveMemory", Emit.NativeCallingConvention.Stdcall, false, "__RtlMoveMemoryRef", ARGS_RTLMOVEMEMORYREF);
public IFunction RtlMoveMemoryVal => EnsureDllImportedFunctionCreated(ref m_RtlMoveMemoryVal, "ntdll.dll", "RtlMoveMemory", Emit.NativeCallingConvention.Stdcall, false, "__RtlMoveMemoryVal", ARGS_RTLMOVEMEMORYVAL);
public IFunction ComInterfaceCast
{
get
{
if (m_ComInterfaceCast == null)
{
m_ComInterfaceCast = new Emit.ExternalFunction();
var cls = new Emit.FDecl.Class();
cls.ClassName = "Class";
cls.FunctionName = "CastToType";
cls.CallingConvention = Emit.NativeCallingConvention.Pascal;
m_ComInterfaceCast.Declaration = cls;
m_ComInterfaceCast.Name = "ComInterfaceCast";
m_ComInterfaceCast.ReturnArgument = Types.UnknownType.Instance;
m_ComInterfaceCast.Arguments = ARGS_COMINTERFACECAST;
m_ComInterfaceCast.Exported = true;
Script.Functions.Add(m_ComInterfaceCast);
}
return m_ComInterfaceCast;
}
}
public IFunction VarType
{
get
{
if (m_FunctionsExternal.TryGetValue("VarType", out var func)) return func;
if (m_VarType == null)
{
m_VarType = new Emit.ExternalFunction();
var decl = new Emit.FDecl.Internal();
m_VarType.Declaration = decl;
m_VarType.Name = "VarType";
m_VarType.ReturnArgument = Types.UnknownType.Instance;
m_VarType.Arguments = ARGS_VARTYPE;
m_VarType.Exported = true;
Script.Functions.Add(m_VarType);
m_FunctionsExternal.Add("VarType", m_VarType);
}
return m_VarType;
}
}
public IFunction SetArrayLength
{
get
{
if (m_FunctionsExternal.TryGetValue("SetArrayLength", out var func)) return func;
if (m_SetArrayLength == null)
{
m_SetArrayLength = new Emit.ExternalFunction();
var decl = new Emit.FDecl.Internal();
m_SetArrayLength.Declaration = decl;
m_SetArrayLength.Name = "SetArrayLength";
m_SetArrayLength.ReturnArgument = null;
m_SetArrayLength.Arguments = ARGS_EMPTY;
m_SetArrayLength.Exported = true;
Script.Functions.Add(m_SetArrayLength);
m_FunctionsExternal.Add("SetArrayLength", m_SetArrayLength);
}
return m_SetArrayLength;
}
}
public IFunction CreateValidPointer
{
get
{
if (m_CreateValidPointer == null)
{
var gen = new FunctionGen("__CreateValidPointer", ABT.StorageClass.STATIC);
m_CreateValidPointer = gen.Function;
m_CreateValidPointer.Arguments = new List<FunctionArgument>() {
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.In,
Name = "pPtr",
Type = TypeU32
},
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.In,
Name = "pObjTypeOf",
Type = TypeU32
},
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.Out,
Name = "outPtr",
Type = TypeArrayOfPointer
}
};
m_CreateValidPointer.ReturnArgument = null;
var insns = m_CreateValidPointer.Instructions;
var pPtr = Emit.Operand.Create(m_CreateValidPointer.CreateArgumentVariable(0));
var pObjTypeOf = Emit.Operand.Create(m_CreateValidPointer.CreateArgumentVariable(1));
var outPtr = Emit.Operand.Create(m_CreateValidPointer.CreateArgumentVariable(2));
// outPtr[0].pPtr = pPtr
gen.Push(Emit.Operand.Create(ABT.ExprType.SIZEOF_POINTER)); // push SIZEOF_POINTER // 1
gen.PushVar(pPtr); // pushvar pPtr // 2
gen.PushVar(outPtr); // pushvar outPtr // 3
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Call, RtlMoveMemoryRef)); // call RtlMoveMemoryRef
gen.Pop(); // 2
gen.Pop(); // 1
// outPtr[0].pType = pObjTypeOf.pType
var Var2 = gen.Push(pObjTypeOf).Variable; // push pObjTypeOf // 2
insns.Add(Emit.Instruction.Create<uint>(Emit.OpCodes.Sub, Var2, ABT.ExprType.SIZEOF_POINTER)); // sub Var2, SIZEOF_POINTER
var Var3 = gen.PushType(TypeU32); // pushtype U32 // 3
gen.PushVar(outPtr); // pushvar outPtr // 4
gen.PushVar(Var3); // pushvar Var3 // 5
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Call, CastPointerRef)); // call CastPointerRef
gen.Pop(); // 4
gen.Pop(); // 3
insns.Add(Emit.Instruction.Create<uint>(Emit.OpCodes.Add, Var3.Variable, ABT.ExprType.SIZEOF_POINTER)); // add Var3, SIZEOF_POINTER
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Call, RtlMoveMemoryVal));
// return;
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Ret));
Script.Functions.Add(m_CreateValidPointer);
}
return m_CreateValidPointer;
}
}
public IFunction CastRefPointer
{
get
{
if (m_CastRefPointer == null)
{
var gen = new FunctionGen("__CastRefPointer", ABT.StorageClass.STATIC);
m_CastRefPointer = gen.Function;
m_CastRefPointer.Arguments = new List<FunctionArgument>() {
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.Out,
Name = "pType",
Type = TypeU32 // actually anytype, runtime doesn't care about the type :)
},
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.In,
Name = "pVal",
Type = TypeU32
},
new FunctionArgument()
{
ArgumentType = FunctionArgumentType.Out,
Name = "outPtr",
Type = TypeArrayOfPointer
}
};
m_CastRefPointer.ReturnArgument = null;
var insns = m_CastRefPointer.Instructions;
var pType = Emit.Operand.Create(m_CastRefPointer.CreateArgumentVariable(0));
var pVal = Emit.Operand.Create(m_CastRefPointer.CreateArgumentVariable(1));
var outPtr = Emit.Operand.Create(m_CastRefPointer.CreateArgumentVariable(2));
// CreateValidPointer(pVal, (u32)pType, outPtr);
gen.PushVar(outPtr);
var Var2 = gen.PushType(TypeU32);
gen.PushVar(pType);
gen.PushVar(Var2);
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Call, CastPointerRef)); // call CastPointerRef
gen.Pop();
gen.Pop();
gen.Push(pVal);
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Call, CreateValidPointer)); // call CreateValidPointer
// return;
insns.Add(Emit.Instruction.Create(Emit.OpCodes.Ret));
Script.Functions.Add(m_CastRefPointer);
}
return m_CastRefPointer;
}
}
private Dictionary<string, Types.IType> m_TypesCache = new Dictionary<string, Types.IType>();
private Dictionary<string, ABT.ExprType> m_TypesCacheNamed = new Dictionary<string, ABT.ExprType>();
private static Guid GUID_IDISPATCH = new Guid(0x00020400, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
private static Guid GUID_IUNKNOWN = new Guid(0x00000000, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
public static bool TypeIsIDispatch(ABT.ExprType type)
{
if (type.Kind != ABT.ExprTypeKind.COM_INTERFACE) return false;
return ((ABT.ComInterfaceType)type).InterfaceGuid == GUID_IDISPATCH;
}
public static bool TypeImplementsIDispatch(ABT.ExprType type)
{
if (type.Kind != ABT.ExprTypeKind.COM_INTERFACE) return false;
return type.TypeAttribs.Any((attr) => attr.Name == "__dispatch");
}
public static readonly ABT.ComInterfaceType ExprTypeIDispatch = new ABT.ComInterfaceType(GUID_IDISPATCH);
// Fixes up any types that the runtime expects to be exported with a specific name
private static void FixUpTypeForRuntime(ABT.ExprType type, Types.IType emit)
{
// openarray is done by name prefix
if (type.Kind == ABT.ExprTypeKind.INCOMPLETE_ARRAY)
{
if (((ABT.IncompleteArrayType)type).ElemType.TypeAttribs.Any((attr) => attr.Name == "__open"))
{
emit.Name = "!OPENARRAY" + emit.Name;
emit.Exported = true;
}
}
}
public Types.IType EmitType(ABT.ExprType type, bool forGlobal = false)
{
// Check for specific hardcoded types.
switch (type)
{
case ABT.PointerType ptr:
// We can't use type pointer, it's impossible to initialise.
// Use type array of pointer instead.
// for void*, use u32 (void* is impossible to specify, we can cast it later)
if (!ptr.IsRef) return TypeU32;
if (!(ptr.RefType is ABT.FunctionType))
return TypeArrayOfPointer;
break;
case ABT.UCharType u8:
return TypeUByte;
case ABT.UShortType u16:
return TypeU16;
case ABT.ULongType u32:
return TypeU32;
case ABT.ComInterfaceType com:
if (com.InterfaceGuid == GUID_IDISPATCH) return TypeIDispatch;
break;
}
// Check the named cache.
if (m_TypesCacheNamed.TryGetValue(type.ToString(), out var named)) type = named;
// Check the cache.
if (m_TypesCache.TryGetValue(type.ToString(), out var ret)) return ret;
ret = type.Emit(this);
var namable = type as ABT.IExprTypeWithName;
if (namable != null) ret.Name = namable.TypeName.Replace(' ', '_');
FixUpTypeForRuntime(type, ret);
if (ret == null) throw new InvalidOperationException("trying to emit null type");
Script.Types.Add(ret);
m_TypesCache.Add(type.ToString(), ret);
return ret;
}
public void ChangeTypeName(ABT.IExprTypeWithName type, string name)
{
Types.IType emit = null;
var realType = (ABT.ExprType)type;
var old = realType.ToString();
if (m_TypesCacheNamed.TryGetValue(old, out var named))
{
realType = named;
type = (ABT.IExprTypeWithName)named;
}
else m_TypesCacheNamed.Add(old, realType);
if (m_TypesCache.TryGetValue(old, out emit)) m_TypesCache.Remove(old);
type.TypeName = name;
var realName = realType.ToString();
if (emit != null)
{
emit.Name = name.Replace(' ', '_');
FixUpTypeForRuntime(realType, emit);
m_TypesCache.Add(realName, emit);
}
if (!m_TypesCacheNamed.ContainsKey(realName)) m_TypesCacheNamed.Add(realName, realType);
}
private Dictionary<string, Emit.GlobalVariable> m_Globals = new Dictionary<string, Emit.GlobalVariable>();
public IReadOnlyDictionary<string, Emit.GlobalVariable> Globals => m_Globals;
private Emit.GlobalVariable CreateGlobal(Types.IType type, string name)
{
var ret = Emit.GlobalVariable.Create(Script.GlobalVariables.Count, type, name);
Script.GlobalVariables.Add(ret);
m_Globals.Add(name, ret);
return ret;
}
public Emit.GlobalVariable CreateGlobal(ABT.ExprType type, string name, IStoredLineInfo info)
{
if (m_Globals.ContainsKey(name)) throw new InvalidOperationException(string.Format("Global with name {0} already exists", name)).Attach(info);
return CreateGlobal(EmitType(type, true), name);
}
public CGenState() {
this.label_idx = 2;
this.label_packs = new Stack<LabelPack>();
}
public IFunction GetFunction(string name)
{
if (m_Functions.TryGetValue(name, out var impl)) return impl.Function;
if (!m_FunctionsExternal.TryGetValue(name, out var ext)) throw new KeyNotFoundException();
return ext;
}
private void DeclareFunctionArguments(FunctionBase func, ABT.FunctionType type)
{
// ensure that script has pointer type
TypePointer.ToString();
if (type.ReturnType is ABT.VoidType)
func.ReturnArgument = null;
else
func.ReturnArgument = EmitType(type.ReturnType);
func.Arguments = new List<FunctionArgument>();
foreach (var arg in type.Args)
{
var farg = new FunctionArgument();
farg.Name = arg.name;
switch (arg.type)
{
case ABT.PointerType ptr:
// void* == u32
if (!ptr.IsRef)
{
break;
}
farg.ArgumentType = FunctionArgumentType.Out; //ref
farg.Type = EmitType(ptr.RefType);
break;
default:
break;
}
if (farg.Type == null)
{
farg.ArgumentType = FunctionArgumentType.In;
farg.Type = EmitType(arg.type);
}
func.Arguments.Add(farg);
}
}
private FunctionGen DeclareFunctionCore(string name, ABT.FunctionType type, ABT.StorageClass scs)
{
if (m_Functions.ContainsKey(name)) return null;
if (m_FunctionsExternal.ContainsKey(name)) return null;
var state = new FunctionGen(name, scs);
DeclareFunctionArguments(state.Function, type);
m_Functions[name] = state;
Script.Functions.Add(state.Function);
return state;
}
public void DeclareFunction(string name, ABT.FunctionType type, ABT.StorageClass scs)
{
DeclareFunctionCore(name, type, scs);
}
public void DeclareExternalFunction(string name, Emit.ExternalFunction func, ABT.FunctionType type, IStoredLineInfo info)
{
if (m_Functions.ContainsKey(name) || m_FunctionsExternal.ContainsKey(name)) throw new InvalidOperationException(string.Format("Already declared a function with name {0}", name)).Attach(info);
func.Name = name;
DeclareFunctionArguments(func, type);
m_FunctionsExternal[name] = func;
Script.Functions.Add(func);
}
public void CGenFuncStart(string name, ABT.FunctionType type, ABT.StorageClass scs, IStoredLineInfo info) {
if (m_Functions.TryGetValue(name, out var state))
{
if (state.Function.Instructions.Count != 0) throw new InvalidOperationException(string.Format("Already emitted instructions for function {0}", name)).Attach(info);
FunctionState = state;
return;
}
if (m_FunctionsExternal.ContainsKey(name)) throw new InvalidOperationException(string.Format("Already declared an external function with name {0}", name)).Attach(info);
FunctionState = DeclareFunctionCore(name, type, scs);
}
// CGenExpandStack
// ===============
//
public void CGenForceStackSizeTo(Int32 nbytes) {
if (!FunctionState.IndexToLabel.ContainsKey(CurrInsns.Count - 1))
{
var lastop = CurrInsns.LastOrDefault()?.OpCode.Code;
if (lastop == null || lastop == Emit.Code.Jump || lastop == Emit.Code.Ret) return;
}
while (StackSize > nbytes) FunctionState.Pop();
}
private Stack<int> stackSizes = new Stack<int>();
public void CGenPushStackSize()
{
stackSizes.Push(StackSize);
}
public void CGenPeekStackSize()
{
if (stackSizes.Count == 0) return;
CGenForceStackSizeTo(stackSizes.Peek());
}
public void CGenPopStackSize()
{
if (stackSizes.Count == 0) return;
CGenForceStackSizeTo(stackSizes.Pop());
}
public void CGenPopStackSizeForRevert()
{
if (stackSizes.Count == 0) return;
var expected = stackSizes.Pop();
while (StackSize > expected) FunctionState.PopForRevert();
}
public void CGenLabel(Int32 label)
{
if (FunctionState.IndexToLabel.ContainsKey(CurrInsns.Count - 1))
{
var lbl = FunctionState.Labels[label];
// For each instruction referencing this label: replace the operand with CurrInsns.Last()
var jump = CurrInsns.Last();
foreach (var insn in CurrInsns)
{
switch (insn.OpCode.OperandType)
{
case Emit.OperandType.InlineBrTarget:
case Emit.OperandType.InlineBrTargetValue:
if (insn[0].ImmediateAs<Emit.Instruction>() == lbl) insn[0] = Emit.Operand.Create(jump);
break;
case Emit.OperandType.InlineEH:
if (insn[0].ImmediateAs<Emit.Instruction>() == lbl) insn[0] = Emit.Operand.Create(jump);
if (insn[1].ImmediateAs<Emit.Instruction>() == lbl) insn[1] = Emit.Operand.Create(jump);
if (insn[2].ImmediateAs<Emit.Instruction>() == lbl) insn[2] = Emit.Operand.Create(jump);
if (insn[3].ImmediateAs<Emit.Instruction>() == lbl) insn[3] = Emit.Operand.Create(jump);
break;
}
}
FunctionState.Labels[label] = jump;
return;
}
var idx = CurrInsns.Count;
CurrInsns.Add(FunctionState.Labels[label]);
FunctionState.LabelToIndex.Add(label, idx);
FunctionState.IndexToLabel.Add(idx, label);
}
public Int32 label_idx;
public Int32 StackSize => FunctionState.LocalsCount;
public Int32 RequestLabel() {
this.label_idx = FunctionState.Labels.Count;
var ret = label_idx;
FunctionState.Labels.Add(Emit.Instruction.Create(Emit.OpCodes.Nop));
return ret;
}
//private Stack<Int32> _continue_labels;
//private Stack<Int32> _break_labels;
private struct LabelPack {
public LabelPack(Int32 continue_label, Int32 break_label, Int32 default_label, Dictionary<Int32, Int32> value_to_label) {
this.continue_label = continue_label;
this.break_label = break_label;
this.default_label = default_label;
this.value_to_label = value_to_label;
}
public readonly Int32 continue_label;
public readonly Int32 break_label;
public readonly Int32 default_label;
public readonly Dictionary<Int32, Int32> value_to_label;
}
private readonly Stack<LabelPack> label_packs;
public Int32 ContinueLabel => this.label_packs.First(_ => _.continue_label != -1).continue_label;
public Int32 BreakLabel => this.label_packs.First(_ => _.break_label != -1).break_label;
public Int32 DefaultLabel {
get {
Int32 ret = this.label_packs.First().default_label;
if (ret == -1) {
throw new InvalidOperationException("Not in a switch statement.");
}
return ret;
}
}
public bool IsInSwitch => label_packs.Count > 0 ? label_packs.Peek().value_to_label != null : false;
public Int32 CaseLabel(Int32 value) => this.label_packs.First(_ => _.value_to_label != null).value_to_label[value];
// label_packs.First().value_to_label[Value];
public void InLoop(Int32 continue_label, Int32 break_label) {
this.label_packs.Push(new LabelPack(continue_label, break_label, -1, null));
//_continue_labels.Push(continue_label);
//_break_labels.Push(break_label);
}
public void InSwitch(Int32 break_label, Int32 default_label, Dictionary<Int32, Int32> value_to_label) {
this.label_packs.Push(new LabelPack(-1, break_label, default_label, value_to_label));
}
public void OutLabels() {
this.label_packs.Pop();
//_continue_labels.Pop();
//_break_labels.Pop();
}
private readonly Dictionary<String, Int32> _goto_labels = new Dictionary<String, Int32>();
public Int32 GotoLabel(String label) {
return this._goto_labels[label];
}
public void JumpTo(int label, bool ignoreStack)
{
if (FunctionState.IndexToLabel.TryGetValue(CurrInsns.Count - 1, out var lastLabel))
{
var lbl = CurrInsns.Last();
// For each instruction referencing this label: replace the operand with CurrInsns.Last()
var jump = FunctionState.Labels[label];
foreach (var insn in CurrInsns)
{
switch (insn.OpCode.OperandType)
{
case Emit.OperandType.InlineBrTarget:
case Emit.OperandType.InlineBrTargetValue:
if (insn[0].ImmediateAs<Emit.Instruction>() == lbl) insn[0] = Emit.Operand.Create(jump);
break;
case Emit.OperandType.InlineEH:
if (insn[0].ImmediateAs<Emit.Instruction>() == lbl) insn[0] = Emit.Operand.Create(jump);
if (insn[1].ImmediateAs<Emit.Instruction>() == lbl) insn[1] = Emit.Operand.Create(jump);
if (insn[2].ImmediateAs<Emit.Instruction>() == lbl) insn[2] = Emit.Operand.Create(jump);
if (insn[3].ImmediateAs<Emit.Instruction>() == lbl) insn[3] = Emit.Operand.Create(jump);
break;
}
}
FunctionState.Labels[lastLabel] = jump;
return;
}
if (!ignoreStack) CGenPeekStackSize();
CurrInsns.Add(Emit.Instruction.Create(Emit.OpCodes.Jump, FunctionState.Labels[label]));
}
public void InFunction(IReadOnlyList<String> goto_labels) {
this._goto_labels.Clear();
foreach (String goto_label in goto_labels) {
this._goto_labels.Add(goto_label, RequestLabel());
}
}
public void OutFunction() {
this._goto_labels.Clear();
// Optimisation: replace any nop instruction at label with instruction after.
foreach (var label in FunctionState.LabelToIndex.OrderByDescending((l) => l.Value))
{
if (CurrInsns.Count > label.Value && CurrInsns[label.Value].OpCode.Code == Emit.Code.Nop)
{
CurrInsns[label.Value].Replace(CurrInsns[label.Value + 1]);
CurrInsns.RemoveAt(label.Value + 1);
}
}
FunctionState.Function.UpdateInstructionOffsets();
FunctionState.Function.UpdateInstructionCrossReferences();
}
public void EmitCallsToCtor()
{
// if the constructor function wasn't created (ie, no initialised global variables), nothing needs to be done
if (m_Constructor == null) return;
// Emit the return instruction for the constructor.
m_Constructor.Function.Instructions.Add(Emit.Instruction.Create(Emit.OpCodes.Ret));
// Enumerate through all public functions (non-static).
var publicFunctions = m_Functions.Values.Select(f => f.Function).Where(f => f.Exported);
foreach (var f in publicFunctions)
{
// Add an instruction to the start to call the constructor.
f.Instructions.Insert(0, Emit.Instruction.Create(Emit.OpCodes.Call, m_Constructor.Function as IFunction));
}
}
}
}