Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e814c25513069cc499028a77f499b5a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@

using UdonSharp;
using UnityEditor;
using VRC.SDKBase.Editor.BuildPipeline;
namespace UdonSharpEditor
{
internal class UdonSharpBuildChecks : IVRCSDKBuildRequestedCallback
{
public int callbackOrder => -1;
/// <summary>
/// If you're considering commenting any section of this out, try enabling the force compile in the U# settings first.
/// This is here to prevent you from corrupting your project files.
/// If scripts are left uncompiled from Unity's side when uploading, there is a chance to corrupt your assemblies which can cause all of your UdonBehaviours to lose their variables if handled wrong.
/// </summary>
/// <param name="requestedBuildType"></param>
/// <returns></returns>
public bool OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType)
{
UdonSharpSettings settings = UdonSharpSettings.GetSettings();
bool shouldForceCompile = settings.shouldForceCompile;
// Unity doesn't like this and will throw errors if it ends up compiling scripts. But it seems to work.
// This is marked experimental for now since I don't know if it will break horribly in some case.
if (shouldForceCompile)
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate | ImportAssetOptions.ForceSynchronousImport);
}
else
{
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
if (EditorApplication.isCompiling)
{
UdonSharpUtils.LogWarning("Scripts are in the process of compiling, please retry build after scripts have compiled.");
UdonSharpUtils.ShowEditorNotification("Scripts are in the process of compiling, please retry build after scripts have compiled.");
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67ef2f829963ac94db0d01f19c3a1caf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@

using UdonSharp;
using UdonSharp.Compiler;
using VRC.SDKBase.Editor.BuildPipeline;
namespace UdonSharpEditor
{
internal class UdonSharpBuildCompile : IVRCSDKBuildRequestedCallback
{
public int callbackOrder => 100;
public bool OnBuildRequested(VRCSDKRequestedBuildType requestedBuildType)
{
if (requestedBuildType == VRCSDKRequestedBuildType.Avatar)
return true;
if (UdonSharpSettings.GetSettings().disableUploadCompile)
return true;
UdonSharpCompilerV1.CompileSync(new UdonSharpCompileOptions() { IsEditorBuild = false });
UdonSharpEditorCache.SaveAllCache();
if (UdonSharpProgramAsset.AnyUdonSharpScriptHasError())
{
UdonSharpUtils.LogError("Failed to compile UdonSharp scripts for build, check error log for details.");
UdonSharpUtils.ShowEditorNotification("Failed to compile UdonSharp scripts for build, check error log for details.");
return false;
}
if (UdonSharpEditorManager.RunAllUpgrades())
{
UdonSharpUtils.LogWarning(UdonSharpEditorManager.UPGRADE_MESSAGE);
return false;
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32fdba3afe6c0a74d8714d0803e14c1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 511bc0d84cc7ac341ba11007cab661cf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,96 @@
using System;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler
{
/// <summary>
/// Base representation of a phase of compilation
/// </summary>
internal abstract class AbstractPhaseContext
{
public CompilationContext CompileContext { get; }
public SyntaxNode CurrentNode { get; set; }
protected AbstractPhaseContext(CompilationContext compileContext)
{
CompileContext = compileContext;
}
public TypeSymbol GetTypeSymbol(ITypeSymbol type)
{
TypeSymbol typeSymbol = CompileContext.GetTypeSymbol(type, this);
typeSymbol = (TypeSymbol)RedirectTypeSymbol(typeSymbol);
OnSymbolRetrieved(typeSymbol);
return typeSymbol;
}
public TypeSymbol GetTypeSymbolWithoutRedirect(ITypeSymbol type)
{
TypeSymbol typeSymbol = CompileContext.GetTypeSymbol(type, this);
OnSymbolRetrieved(typeSymbol);
return typeSymbol;
}
public TypeSymbol GetUdonTypeSymbol(ITypeSymbol type)
{
TypeSymbol typeSymbol = CompileContext.GetUdonTypeSymbol(type, this);
typeSymbol = (TypeSymbol)RedirectTypeSymbol(typeSymbol);
OnSymbolRetrieved(typeSymbol);
return typeSymbol;
}
public TypeSymbol GetTypeSymbol(Type systemType)
{
TypeSymbol typeSymbol = CompileContext.GetTypeSymbol(systemType, this);
typeSymbol = (TypeSymbol)RedirectTypeSymbol(typeSymbol);
OnSymbolRetrieved(typeSymbol);
return typeSymbol;
}
public TypeSymbol GetTypeSymbol(SpecialType type)
{
TypeSymbol typeSymbol = CompileContext.GetTypeSymbol(type, this);
typeSymbol = (TypeSymbol)RedirectTypeSymbol(typeSymbol);
OnSymbolRetrieved(typeSymbol);
return typeSymbol;
}
public Symbol GetSymbol(ISymbol sourceSymbol)
{
Symbol symbol = CompileContext.GetSymbol(sourceSymbol, this);
symbol = RedirectTypeSymbol(symbol);
symbol = RedirectParameterSymbol(symbol);
symbol = RedirectMethodSymbol(symbol);
OnSymbolRetrieved(symbol);
return symbol;
}
public Symbol GetSymbolNoRedirect(ISymbol sourceSymbol)
{
Symbol symbol = CompileContext.GetSymbol(sourceSymbol, this);
OnSymbolRetrieved(symbol);
return symbol;
}
public void MarkSymbolReferenced(Symbol symbol)
{
OnSymbolRetrieved(symbol);
}
protected virtual void OnSymbolRetrieved(Symbol symbol)
{
}
protected virtual Symbol RedirectTypeSymbol(Symbol symbol) => symbol;
protected virtual Symbol RedirectMethodSymbol(Symbol symbol) => symbol;
protected virtual Symbol RedirectParameterSymbol(Symbol symbol) => symbol;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bcc0ab02ef3e9d84996c029106dfdda6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43efb9a5e72cddc4797f9f58eabbe2ab
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,393 @@

using System;
using System.Text;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Assembly
{
internal enum InstructionKind
{
Nop,
Push,
Pop,
Copy,
Jump,
JumpIfFalse,
Extern,
ExternSet,
ExternGet,
JumpIndirect,
Return,
Comment,
ExportTag,
SyncTag,
}
internal abstract class AssemblyInstruction
{
public virtual InstructionKind GetKind() => throw new NotImplementedException();
public virtual uint Size => throw new NotImplementedException();
public uint InstructionAddress { get; set; } = UInt32.MaxValue;
public string Comment { get; set; }
public abstract void WriteAssembly(StringBuilder builder);
protected void WriteIndentedLine(string str, StringBuilder builder, int indent = 2)
{
if (indent == 2)
builder.AppendFormat(" {0}\n", str);
else
builder.AppendFormat("{0}{1}\n", new string(' ', indent * 4), str);
}
}
namespace Instructions
{
internal class NopInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Nop;
public override uint Size => 4;
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine("NOP", builder);
}
public override string ToString()
{
return "NOP";
}
}
internal class Comment : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Comment;
public override uint Size => 0;
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"# {Comment}", builder, 0);
}
public Comment(string comment)
{
Comment = comment;
}
public override string ToString()
{
return "# " + Comment;
}
}
internal class ExportTag : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.ExportTag;
public override uint Size => 0;
public UdonSharpBehaviourMethodSymbol ExportedMethod { get; }
public ExportTag(UdonSharpBehaviourMethodSymbol methodSymbol)
{
ExportedMethod = methodSymbol;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($".export {ExportedMethod.ExportedMethodAddress.AddressString}", builder, 1);
WriteIndentedLine($"{ExportedMethod.ExportedMethodAddress.AddressString}:", builder, 1);
}
public override string ToString()
{
return "export " + ExportedMethod.ExportedMethodAddress.AddressString;
}
}
internal class FieldCallbackExportTag : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.ExportTag;
public override uint Size => 0;
public UdonSharpBehaviourFieldSymbol ExportedField { get; }
public FieldCallbackExportTag(UdonSharpBehaviourFieldSymbol fieldSymbol)
{
ExportedField = fieldSymbol;
}
public override void WriteAssembly(StringBuilder builder)
{
string fieldChangeName = $"_onVarChange_{ExportedField.Name}";
WriteIndentedLine($".export {fieldChangeName}", builder, 1);
WriteIndentedLine($"{fieldChangeName}:", builder, 1);
}
public override string ToString()
{
return "export field change" + ExportedField.Name;
}
}
internal class SyncTag : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.SyncTag;
public override uint Size => 0;
public Value SyncedValue { get; }
public UdonSyncMode SyncMode { get; }
public SyncTag(Value syncedValue, UdonSyncMode syncMode)
{
SyncedValue = syncedValue;
SyncMode = syncMode;
}
public override void WriteAssembly(StringBuilder builder)
{
// WriteIndentedLine($".export {}");
}
public override string ToString()
{
return $"sync {SyncedValue}, {SyncMode}";
}
}
internal class PushInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Push;
public override uint Size => 8;
public Value PushValue { get; }
public PushInstruction(Value value)
{
PushValue = value;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"PUSH, {PushValue.UniqueID}", builder);
}
public override string ToString()
{
return "PUSH: " + PushValue;
}
}
internal class PopInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Pop;
public override uint Size => 4;
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"POP", builder);
}
public override string ToString()
{
return "POP";
}
}
internal class CopyInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Copy;
public override uint Size => 20;
public Value SourceValue { get; }
public Value TargetValue { get; }
public CopyInstruction(Value sourceValue, Value targetValue)
{
SourceValue = sourceValue;
TargetValue = targetValue;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"PUSH, {SourceValue.UniqueID}", builder);
WriteIndentedLine($"PUSH, {TargetValue.UniqueID}", builder);
WriteIndentedLine($"COPY", builder);
}
public override string ToString()
{
return $"COPY: {SourceValue} -> {TargetValue}";
}
}
internal class JumpInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Jump;
public override uint Size => 8;
public JumpLabel JumpTarget { get; private set; }
public JumpInstruction(JumpLabel jumpTarget)
{
JumpTarget = jumpTarget;
}
public override void WriteAssembly(StringBuilder builder)
{
if (JumpTarget.Address == uint.MaxValue)
throw new InvalidOperationException($"Cannot jump to uninitialized jump label!" + (JumpTarget.DebugMethod == null ? "" : $" Target method: {JumpTarget.DebugMethod}"));
WriteIndentedLine($"JUMP, 0x{JumpTarget.Address:X8}", builder);
}
public override string ToString()
{
return $"JUMP: {JumpTarget}";
}
}
internal class JumpIfFalseInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.JumpIfFalse;
public override uint Size => 16;
public JumpLabel JumpTarget { get; }
public Value ConditionValue { get; }
public JumpIfFalseInstruction(JumpLabel jumpTarget, Value conditionValue)
{
JumpTarget = jumpTarget;
ConditionValue = conditionValue;
}
public override void WriteAssembly(StringBuilder builder)
{
if (JumpTarget.Address == uint.MaxValue)
throw new InvalidOperationException($"Cannot jump to uninitialized jump label!" + (JumpTarget.DebugMethod == null ? "" : $" Target method: {JumpTarget.DebugMethod}"));
WriteIndentedLine($"PUSH, {ConditionValue.UniqueID}", builder);
WriteIndentedLine($"JUMP_IF_FALSE, 0x{JumpTarget.Address:x8}", builder);
}
public override string ToString()
{
return $"JUMP_IF_FALSE: target: {JumpTarget}, condition {ConditionValue}";
}
}
internal class JumpIndirectInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.JumpIndirect;
public override uint Size => 8;
public Value JumpTargetValue { get; }
public JumpIndirectInstruction(Value jumpTargetValue)
{
JumpTargetValue = jumpTargetValue;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"JUMP_INDIRECT, {JumpTargetValue.UniqueID}", builder);
}
public override string ToString()
{
return $"JUMP_INDIRECT: {JumpTargetValue}";
}
}
internal class ExternInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Extern;
public override uint Size => 8;
public IExternSymbol Extern { get; }
public ExternInstruction(IExternSymbol @extern)
{
Extern = @extern;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"EXTERN, \"{Extern.ExternSignature}\"", builder);
}
public override string ToString()
{
return $"EXTERN: {Extern}, {Extern.ExternSignature}";
}
}
internal class ExternSetInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.ExternSet;
public override uint Size => 8;
public IExternAccessor Extern { get; }
public ExternSetInstruction(IExternAccessor externSet)
{
Extern = externSet;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"EXTERN, \"{Extern.ExternSetSignature}\"", builder);
}
}
internal class ExternGetInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.ExternGet;
public override uint Size => 8;
public IExternAccessor Extern { get; }
public ExternGetInstruction(IExternAccessor externGet)
{
Extern = externGet;
}
public override void WriteAssembly(StringBuilder builder)
{
WriteIndentedLine($"EXTERN, \"{Extern.ExternGetSignature}\"", builder);
}
}
internal class RetInstruction : AssemblyInstruction
{
public override InstructionKind GetKind() => InstructionKind.Return;
public override uint Size => 20; // Push, Copy, JumpIndirect
// public override uint Size => 12; // Temp force exit jump
public Value RetValRef { get; }
public RetInstruction(Value retVal)
{
RetValRef = retVal;
}
public override void WriteAssembly(StringBuilder builder)
{
// WriteIndentedLine("POP", builder);
// WriteIndentedLine("JUMP, 0xFFFFFFF0", builder);
WriteIndentedLine($"PUSH, {RetValRef.UniqueID}", builder);
WriteIndentedLine("COPY", builder);
WriteIndentedLine($"JUMP_INDIRECT, {RetValRef.UniqueID}", builder);
}
public override string ToString()
{
return "RET";
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08d479294d790bf48b1acd11f27985c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,256 @@

using System;
using System.Collections.Generic;
using System.Text;
using UdonSharp.Compiler.Assembly.Instructions;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Assembly
{
/// <summary>
/// Contains the assembly for a given module of compilation.
/// A module in this context is all code and associated heap values that will be compiled into a single program asset.
/// This includes primary events and methods on a given UdonBehaviour, along with all imported methods for referenced types.
/// </summary>
internal class AssemblyModule
{
public CompilationContext CompileContext { get; }
private List<AssemblyInstruction> _instructions = new List<AssemblyInstruction>();
private List<JumpLabel> _jumpLabels = new List<JumpLabel>();
private uint _currentAddress;
public uint CurrentAddress => _currentAddress;
public int ExecutionOrder { get; set; }
public ValueTable RootTable { get; }
public Dictionary<Symbol, ExportAddress> Exports { get; } = new Dictionary<Symbol, ExportAddress>();
public AssemblyModule(CompilationContext context)
{
CompileContext = context;
RootTable = new ValueTable(this, null);
}
public AssemblyInstruction this[int index]
{
get
{
if (index < 0 || index >= _instructions.Count)
throw new IndexOutOfRangeException("Instruction index is not valid");
return _instructions[index];
}
}
public int InstructionCount => _instructions.Count;
#region Instruction Adds
private void AddInstruction(AssemblyInstruction instruction)
{
_instructions.Add(instruction);
instruction.InstructionAddress = _currentAddress;
_currentAddress += instruction.Size;
}
public void AddNop()
{
AddInstruction(new NopInstruction());
}
public void AddCommentTag(string comment)
{
AddInstruction(new Comment(comment));
}
public void AddExportTag(UdonSharpBehaviourMethodSymbol exportMethod)
{
AddInstruction(new ExportTag(exportMethod));
}
public void AddFieldChangeExportTag(UdonSharpBehaviourFieldSymbol fieldSymbol)
{
AddInstruction(new FieldCallbackExportTag(fieldSymbol));
}
public void AddSyncTag(Value syncValue, UdonSyncMode syncMode)
{
AddInstruction(new SyncTag(syncValue, syncMode));
}
public void AddPush(Value pushValue)
{
if (pushValue == null)
throw new ArgumentNullException(nameof(pushValue));
AddInstruction(new PushInstruction(pushValue));
}
public void AddPop()
{
AddInstruction(new PopInstruction());
}
public void AddCopy(Value sourceValue, Value targetValue)
{
if (targetValue.IsConstant)
throw new ArgumentException("Cannot copy to const value");
AddInstruction(new CopyInstruction(sourceValue, targetValue));
}
public void AddJump(JumpLabel jumpTarget)
{
AddInstruction(new JumpInstruction(jumpTarget));
}
public void AddJumpIfFalse(JumpLabel jumpTarget, Value conditionValue)
{
AddInstruction(new JumpIfFalseInstruction(jumpTarget, conditionValue));
}
public void AddJumpIndrect(Value targetJumpValue)
{
AddInstruction(new JumpIndirectInstruction(targetJumpValue));
}
public void AddExtern(IExternSymbol method)
{
AddInstruction(new ExternInstruction(method));
}
public void AddExternSet(IExternAccessor externAccessor)
{
AddInstruction(new ExternSetInstruction(externAccessor));
}
public void AddExternGet(IExternAccessor externAccessor)
{
AddInstruction(new ExternGetInstruction(externAccessor));
}
public void AddReturn(Value returnVal)
{
AddInstruction(new RetInstruction(returnVal));
}
#endregion
#region Jump Labels
public JumpLabel CreateLabel()
{
JumpLabel newLabel = new JumpLabel();
_jumpLabels.Add(newLabel);
return newLabel;
}
public void LabelJump(JumpLabel label)
{
if (label.Address != uint.MaxValue)
throw new ArgumentException("Label has already been set");
label.Address = _currentAddress;
}
#endregion
#region Address Allocation
public void AddAddress(ExportAddress address)
{
Exports.Add(address.AddressSymbol, address);
}
#endregion
private void BuildDataBlock(StringBuilder builder)
{
List<Value> allValues = RootTable.GetAllUniqueChildValues();
builder.Append(".data_start\n");
foreach (Value value in allValues)
{
if (value.AssociatedSymbol is FieldSymbol fieldSymbol)
{
if (fieldSymbol.IsSerialized)
builder.AppendFormat(" .export {0}\n", value.UniqueID);
UdonSyncMode? syncMode = fieldSymbol.SyncMode;
switch (syncMode)
{
case UdonSyncMode.None:
builder.AppendFormat(" .sync {0}, none\n", value.UniqueID);
break;
case UdonSyncMode.Linear:
builder.AppendFormat(" .sync {0}, linear\n", value.UniqueID);
break;
case UdonSyncMode.Smooth:
builder.AppendFormat(" .sync {0}, smooth\n", value.UniqueID);
break;
}
}
}
foreach (Value value in allValues)
{
builder.AppendFormat(" {0}\n", value.GetDeclarationStr());
}
builder.Append(".data_end\n");
}
private void BuildInstructionUasm(StringBuilder builder)
{
builder.Append(".code_start\n");
if (ExecutionOrder != 0)
builder.Append($".update_order {ExecutionOrder}\n");
foreach (var instruction in _instructions)
instruction.WriteAssembly(builder);
builder.Append(".code_end\n");
}
public string BuildUasmStr()
{
StringBuilder uasmBuilder = new StringBuilder();
BuildDataBlock(uasmBuilder);
BuildInstructionUasm(uasmBuilder);
return uasmBuilder.ToString();
}
public uint GetHeapSize()
{
int heapValCount = RootTable.GetAllUniqueChildValues().Count;
HashSet<string> uniqueExternCount = new HashSet<string>();
foreach (AssemblyInstruction instruction in _instructions)
{
switch (instruction)
{
case ExternInstruction externMethodSymbol:
uniqueExternCount.Add(externMethodSymbol.Extern.ExternSignature);
break;
case ExternSetInstruction externSetInstruction:
uniqueExternCount.Add(externSetInstruction.Extern.ExternSetSignature);
break;
case ExternGetInstruction externGetInstruction:
uniqueExternCount.Add(externGetInstruction.Extern.ExternGetSignature);
break;
}
}
return (uint)(heapValCount + uniqueExternCount.Count);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cbaa361a12f19c941bdb7989992f4454
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,91 @@

using JetBrains.Annotations;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Assembly
{
internal class ExportAddress
{
public enum AddressKind
{
String,
Uint,
}
public AddressKind Kind { get; }
/// <summary>
/// The symbol this export belongs to
/// </summary>
public Symbol AddressSymbol { get; }
public string AddressString { get; private set; }
public uint AddressUint { get; private set; }
public bool IsResolved { get; private set; }
// public ExportAddress(string address, [NotNull] Symbol symbol)
// {
// Kind = AddressKind.String;
// AddressString = address;
// AddressSymbol = symbol;
// IsResolved = true;
// }
//
// public ExportAddress(uint address, [NotNull] Symbol symbol)
// {
// Kind = AddressKind.Uint;
// AddressUint = address;
// AddressSymbol = symbol;
// IsResolved = true;
// }
public ExportAddress(AddressKind kind, [NotNull] Symbol symbol)
{
Kind = kind;
AddressSymbol = symbol;
}
public void ResolveAddress(uint address)
{
IsResolved = true;
AddressUint = address;
}
public void ResolveAddress(string address)
{
IsResolved = true;
AddressString = address;
}
protected bool Equals(ExportAddress other)
{
return Equals(AddressSymbol, other.AddressSymbol) && AddressString == other.AddressString && AddressUint == other.AddressUint;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ExportAddress) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (AddressSymbol != null ? AddressSymbol.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (AddressString != null ? AddressString.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) AddressUint;
return hashCode;
}
}
public override string ToString()
{
return $"address: {(Kind == AddressKind.String ? AddressString : AddressUint.ToString())}, symbol: {AddressSymbol}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c3e167753e866c64cbdd3c5c2dd4f079
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,12 @@

using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Assembly
{
internal class JumpLabel
{
public MethodSymbol DebugMethod { get; set; }
public uint Address { get; set; } = uint.MaxValue;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 105086e0adf1f75488bd27992509f552
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9f8e17574f242e045b3723a56bd7ecf2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,190 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
/// <summary>
/// The context for the binding phase, this is local to a single type that is being bound
/// </summary>
internal class BindContext : AbstractPhaseContext
{
private ITypeSymbol BindSymbol { get; }
private IEnumerable<Symbol> TypeSymbolsToBind { get; }
private Symbol _currentBindSymbol;
public MethodSymbol CurrentBindMethod => _currentBindSymbol as MethodSymbol;
private HashSet<Symbol> _currentReferencedSymbols;
public BindContext(CompilationContext context, ITypeSymbol bindSymbol, IEnumerable<Symbol> referencedTypeSymbolsToBind)
:base(context)
{
BindSymbol = bindSymbol;
TypeSymbolsToBind = referencedTypeSymbolsToBind;
}
public void Bind()
{
TypeSymbol containingType = GetTypeSymbol(BindSymbol);
if (!containingType.IsBound)
containingType.Bind(this);
foreach (Symbol symbol in TypeSymbolsToBind)
{
if (symbol.IsBound)
continue;
using (OpenMemberBindScope(symbol))
symbol.Bind(this);
}
}
protected override void OnSymbolRetrieved(Symbol symbol)
{
if (_currentBindSymbol != null && !symbol.IsExtern)
_currentReferencedSymbols.Add(symbol);
}
private TypeSymbol MakeArraySymbol(TypeSymbol element, int depth)
{
if (element is TypeParameterSymbol)
return element;
TypeSymbol currentSymbol = element;
while (depth-- > 0)
currentSymbol = currentSymbol.MakeArrayType(this);
return currentSymbol;
}
protected override Symbol RedirectTypeSymbol(Symbol symbol)
{
if (_currentBindSymbol == null || !(symbol is TypeParameterSymbol))
return symbol;
TypeSymbol parameterSymbol = (TypeSymbol)symbol;
int typeIdx = -1;
int arrayDepth = 0;
while (parameterSymbol.IsArray)
{
arrayDepth++;
parameterSymbol = parameterSymbol.ElementType;
}
if (arrayDepth > 0)
return MakeArraySymbol((TypeSymbol)RedirectTypeSymbol(parameterSymbol), arrayDepth);
if (_currentBindSymbol is MethodSymbol currentMethodSymbol)
{
if (currentMethodSymbol.TypeArguments.Length > 0 && currentMethodSymbol.OriginalSymbol != null)
{
MethodSymbol originalMethod = (MethodSymbol)currentMethodSymbol.OriginalSymbol;
for (int i = 0; i < originalMethod.TypeArguments.Length; ++i)
{
if (!parameterSymbol.Equals(originalMethod.TypeArguments[i])) continue;
typeIdx = i;
break;
}
if (typeIdx != -1)
return currentMethodSymbol.TypeArguments[typeIdx];
}
}
TypeSymbol containingType = _currentBindSymbol.ContainingType;
if (containingType.TypeArguments.Length > 0 && containingType.OriginalSymbol != null)
{
TypeSymbol originalType = (TypeSymbol)containingType.OriginalSymbol;
for (int i = 0; i < originalType.TypeArguments.Length; ++i)
{
if (!parameterSymbol.Equals(originalType.TypeArguments[i])) continue;
typeIdx = i;
break;
}
if (typeIdx != -1)
return containingType.TypeArguments[typeIdx];
}
return symbol;
}
protected override Symbol RedirectParameterSymbol(Symbol symbol)
{
if (_currentBindSymbol == null || !(symbol is ParameterSymbol))
return symbol;
if (_currentBindSymbol is MethodSymbol methodSymbol && methodSymbol.RoslynSymbol != methodSymbol.RoslynSymbol.OriginalDefinition)
{
foreach (ParameterSymbol methodParam in methodSymbol.Parameters)
{
if (methodParam.OriginalSymbol.Equals(symbol))
return methodParam;
}
}
return symbol;
}
protected override Symbol RedirectMethodSymbol(Symbol symbol)
{
if (symbol is MethodSymbol methodSymbol
&& methodSymbol.IsGenericMethod
&& methodSymbol.IsUntypedGenericMethod)
{
TypeSymbol[] typeSymbols = methodSymbol.TypeArguments.Select(e => (TypeSymbol)RedirectTypeSymbol(e)).ToArray();
if (!typeSymbols.Any(e => e is TypeParameterSymbol))
return methodSymbol.ConstructGenericMethod(this, typeSymbols);
}
return symbol;
}
public IDisposable OpenMemberBindScope(Symbol boundSymbol)
{
return new MemberBindScope(this, boundSymbol);
}
private void FinishSymbolBind()
{
_currentBindSymbol.SetDependencies(_currentReferencedSymbols);
_currentBindSymbol = null;
_currentReferencedSymbols = null;
}
public TypeSymbol GetCurrentReturnType()
{
return (_currentBindSymbol as MethodSymbol)?.ReturnType;
}
private class MemberBindScope : IDisposable
{
private BindContext context;
public MemberBindScope(BindContext context, Symbol bindSymbol)
{
this.context = context;
context._currentBindSymbol = bindSymbol;
context._currentReferencedSymbols = new HashSet<Symbol>();
context.CurrentNode = context._currentBindSymbol.RoslynSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();
}
public void Dispose()
{
context.FinishSymbolBind();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28ac79db21e90a34bb90a4c981fcb1e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f116f846e181258419efe1e77984f5e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d72e1dfd17efea749b583c237a21c2d2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,222 @@

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using NotSupportedException = UdonSharp.Core.NotSupportedException;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundAccessExpression : BoundExpression
{
protected BoundAccessExpression(SyntaxNode node, BoundExpression sourceExpression)
: base(node, sourceExpression)
{
}
public virtual void MarkForcedBaseCall() {}
/// <summary>
/// Emits a set operation on this access
/// </summary>
/// <param name="context"></param>
/// <param name="valueExpression"></param>
/// <returns>Returns the evaluated value of the valueExpression. This is primarily to handle chained assignment expressions correctly.</returns>
public abstract Value EmitSet(EmitContext context, BoundExpression valueExpression);
public static BoundAccessExpression BindAccess(AbstractPhaseContext context, SyntaxNode node, Symbol accessSymbol, BoundExpression symbolExpressionSource)
{
if (node.Kind() == SyntaxKind.ThisExpression)
return new BoundThisAccessExpression(node, ((ParameterSymbol)accessSymbol).Type, symbolExpressionSource);
switch (accessSymbol)
{
case LocalSymbol localSymbol:
return BoundLocalAccessExpression.BindLocalAccess(node, localSymbol);
case ParameterSymbol parameterSymbol:
return new BoundParameterAccessExpression(node, parameterSymbol);
case FieldSymbol fieldSymbol:
return BoundFieldAccessExpression.BindFieldAccess(context, node, fieldSymbol, symbolExpressionSource);
case PropertySymbol propertySymbol:
return BoundPropertyAccessExpression.BindPropertyAccess(context, node, propertySymbol, symbolExpressionSource);
}
throw new System.NotImplementedException();
}
public static BoundAccessExpression BindThisAccess(TypeSymbol typeSymbol)
{
return new BoundThisAccessExpression(null, typeSymbol, null);
}
public static BoundAccessExpression BindAccess(Value value)
{
return new BoundValueAccessExpression(value);
}
public static BoundAccessExpression BindAccess(Value.CowValue value)
{
return new BoundCowValueAccessExpression(value);
}
public static BoundAccessExpression BindElementAccess(AbstractPhaseContext context, SyntaxNode node,
BoundExpression symbolExpressionSource, BoundExpression[] indexers)
{
return new BoundArrayAccessExpression(node, context, symbolExpressionSource, indexers);
}
public static BoundAccessExpression BindElementAccess(AbstractPhaseContext context, SyntaxNode node, PropertySymbol accessSymbol,
BoundExpression symbolExpressionSource, BoundExpression[] indexers)
{
return BoundPropertyAccessExpression.BindPropertyAccess(context, node, accessSymbol,
symbolExpressionSource, indexers);
}
private sealed class BoundLocalAccessExpression : BoundAccessExpression
{
private LocalSymbol AccessSymbol { get; }
public override TypeSymbol ValueType => AccessSymbol.Type;
public BoundLocalAccessExpression(SyntaxNode node, LocalSymbol accessSymbol)
: base(node, null)
{
AccessSymbol = accessSymbol;
}
public static BoundAccessExpression BindLocalAccess(SyntaxNode node, LocalSymbol localSymbol)
{
if (localSymbol.IsConst)
return new BoundConstantExpression(localSymbol.ConstantValue, localSymbol.Type);
return new BoundLocalAccessExpression(node, localSymbol);
}
public override Value EmitValue(EmitContext context)
{
if (AccessSymbol.IsConst)
return context.GetConstantValue(ValueType, AccessSymbol.ConstantValue);
return context.GetUserValue(AccessSymbol);
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
return context.EmitValueAssignment(context.GetUserValue(AccessSymbol), valueExpression);
}
}
private sealed class BoundParameterAccessExpression : BoundAccessExpression
{
private ParameterSymbol AccessSymbol { get; }
public override TypeSymbol ValueType => AccessSymbol.Type;
public BoundParameterAccessExpression(SyntaxNode node, ParameterSymbol accessSymbol)
: base(node, null)
{
AccessSymbol = accessSymbol;
}
private Value GetParamValue(EmitContext context)
{
MethodSymbol containingMethod = AccessSymbol.ContainingSymbol;
EmitContext.MethodLinkage linkage = context.GetMethodLinkage(containingMethod, false);
int paramIdx = -1;
for (int i = 0; i < containingMethod.Parameters.Length; ++i)
{
if (containingMethod.Parameters[i] == AccessSymbol)
{
paramIdx = i;
break;
}
}
return linkage.ParameterValues[paramIdx];
}
public override Value EmitValue(EmitContext context)
{
return GetParamValue(context);
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
return context.EmitValueAssignment(GetParamValue(context), valueExpression);
}
}
private sealed class BoundThisAccessExpression : BoundAccessExpression
{
public BoundThisAccessExpression(SyntaxNode node, TypeSymbol thisType, BoundExpression sourceExpression) : base(node, sourceExpression)
{
ValueType = thisType;
IsThis = true;
}
public override TypeSymbol ValueType { get; }
public override Value EmitValue(EmitContext context)
{
if (!((INamedTypeSymbol) ValueType.RoslynSymbol).IsUdonSharpBehaviour())
throw new NotImplementedException("Non udonsharp behaviour `this` is not yet implemented");
return context.GetUdonThisValue(ValueType);
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
throw new NotSupportedException("Cannot set `this` reference", SyntaxNode.GetLocation());
}
}
private sealed class BoundValueAccessExpression : BoundAccessExpression
{
private Value AccessValue { get; }
public override TypeSymbol ValueType => AccessValue.UserType;
public BoundValueAccessExpression(Value value)
: base(null, null)
{
AccessValue = value;
}
public override Value EmitValue(EmitContext context)
{
return AccessValue;
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
return context.EmitValueAssignment(AccessValue, valueExpression);
}
}
private sealed class BoundCowValueAccessExpression : BoundAccessExpression
{
private Value.CowValue AccessValue { get; }
public override TypeSymbol ValueType { get; }
public BoundCowValueAccessExpression(Value.CowValue value)
: base(null, null)
{
AccessValue = value;
ValueType = value.Value.UserType;
}
public override Value EmitValue(EmitContext context)
{
return AccessValue.Value;
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
return context.EmitValueAssignment(AccessValue.Value, valueExpression);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe318b910c0dee1499132005ed0ebdc9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,42 @@

using System;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundArrayAccessExpression : BoundPropertyAccessExpression
{
public BoundArrayAccessExpression(SyntaxNode node, AbstractPhaseContext context, BoundExpression sourceExpression, BoundExpression[] indexerExpressions)
: base(context, node, BuildProperty(context, sourceExpression), sourceExpression, indexerExpressions)
{
}
public override TypeSymbol ValueType => SourceExpression.ValueType.ElementType;
private static PropertySymbol BuildProperty(AbstractPhaseContext context, BoundExpression sourceExpression)
{
TypeSymbol arrayType = sourceExpression.ValueType;
TypeSymbol elementType = arrayType.ElementType;
Type systemType = arrayType.ElementType.UdonType.SystemType;
if (systemType == typeof(UnityEngine.Object) ||
systemType.IsSubclassOf(typeof(UnityEngine.Object)))
{
arrayType = context.GetTypeSymbol(SpecialType.System_Object).MakeArrayType(context);
}
string arrayTypeName = CompilerUdonInterface.GetMethodTypeName(arrayType.UdonType);
string arrayElementTypeName = CompilerUdonInterface.GetUdonTypeName(arrayType.UdonType.ElementType);
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
MethodSymbol setMethod = new ExternSynthesizedMethodSymbol(context, $"{arrayTypeName}.__Set__SystemInt32_{arrayElementTypeName}__SystemVoid",
new[] {intType, elementType}, null, false);
MethodSymbol getMethod = new ExternSynthesizedMethodSymbol(context, $"{arrayTypeName}.__Get__SystemInt32__{arrayElementTypeName}",
new[] {intType}, elementType, false);
return new SynthesizedPropertySymbol(context, getMethod, setMethod);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 988f5122454f3cf46b09b98948019ff4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,64 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal class BoundArrayCreationExpression : BoundExternInvocation
{
private class ArrayConstructorExtern : ExternMethodSymbol
{
public override string ExternSignature { get; }
public override bool IsStatic => true;
public override string Name => ExternSignature;
public ArrayConstructorExtern(AbstractPhaseContext context, TypeSymbol arrayType)
: base(null, context)
{
ExternSignature = $"{arrayType.UdonType.ExternSignature}.__ctor__SystemInt32__{arrayType.UdonType.ExternSignature}";
ReturnType = arrayType;
}
}
private TypeSymbol ArrayType { get; }
private BoundExpression[] Initializers { get; }
public BoundArrayCreationExpression(SyntaxNode node, AbstractPhaseContext context, TypeSymbol arrayType, BoundExpression[] rankSizes, BoundExpression[] initializers)
: base(node, context, new ArrayConstructorExtern(context, arrayType), null, rankSizes)
{
ArrayType = arrayType;
Initializers = initializers;
}
public override TypeSymbol ValueType => ArrayType;
public override Value EmitValue(EmitContext context)
{
Value returnValue = base.EmitValue(context);
if (Initializers != null)
{
BoundAccessExpression arrayAccess = BoundAccessExpression.BindAccess(returnValue);
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
using (context.InterruptAssignmentScope())
{
// This is quite wasteful for allocations in the compile, todo: look at caching these safely
for (int i = 0; i < Initializers.Length; ++i)
{
BoundAccessExpression elementAccess = BoundAccessExpression.BindElementAccess(context,
SyntaxNode, arrayAccess,
new BoundExpression[]
{new BoundConstantExpression(new ConstantValue<int>(i), intType, SyntaxNode)});
context.EmitSet(elementAccess, Initializers[i]);
}
}
}
return returnValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d64aae3d727820c49b7d935b62c0df61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,43 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundAssignmentExpression : BoundExpression
{
private BoundAccessExpression TargetExpression { get; }
public override TypeSymbol ValueType => SourceExpression.ValueType;
public BoundAssignmentExpression(SyntaxNode node, AbstractPhaseContext context, Symbol symbol,
BoundAccessExpression assignmentTarget, BoundExpression instanceExpression, BoundExpression assignmentSource)
: base(node, assignmentSource)
{
if (symbol is ExternFieldSymbol externFieldSymbol)
{
// This is assignment, so check for set only
// Passing originalSymbol.RoslynSymbol to synthesized fields to inherit attributes (static, const, etc.)
TypeSymbol assignmentTargetTypeSymbol = assignmentTarget.ValueType;
ExternFieldSymbol externAlternateFieldSymbol = SetupExternAccessor(node, context, externFieldSymbol, instanceExpression, CompilerUdonInterface.FieldAccessorType.Set,
(candidate) => new ExternSynthesizedFieldSymbol(externFieldSymbol.RoslynSymbol, context, externFieldSymbol.Name, candidate, assignmentTargetTypeSymbol));
// Was an alternate found?
if (externAlternateFieldSymbol != externFieldSymbol)
{
// Ensure the emitted assembly points at the correct left-hand type.
assignmentTarget = BoundFieldAccessExpression.BindFieldAccess(context, node, externAlternateFieldSymbol, instanceExpression);
}
}
TargetExpression = assignmentTarget;
}
public override Value EmitValue(EmitContext context)
{
return context.EmitSet(TargetExpression, SourceExpression);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36e46b7844beaf641b74006dac880f3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,50 @@

using System;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundBitwiseNotExpression : BoundInvocationExpression
{
public override TypeSymbol ValueType => SourceExpression.ValueType;
public BoundBitwiseNotExpression(SyntaxNode node, BoundExpression sourceExpression)
: base(node, null, sourceExpression, new BoundExpression[] {})
{
}
public override Value EmitValue(EmitContext context)
{
Type operandType = ValueType.UdonType.SystemType;
object allBits;
if (UdonSharpUtils.IsSignedType(operandType))
{
allBits = Convert.ChangeType(-1, operandType);
} else
{
allBits = operandType.GetField("MaxValue").GetValue(null);
}
BoundAccessExpression allBitsExpr = BoundAccessExpression.BindAccess(context.GetConstantValue(ValueType, allBits));
Value returnValue = context.GetReturnValue(ValueType);
using (context.InterruptAssignmentScope())
{
BoundAccessExpression operandVal = BoundAccessExpression.BindAccess(context.EmitValue(SourceExpression));
context.EmitValueAssignment(returnValue,
CreateBoundInvocation(context, null,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.LogicalXor, ValueType, context), null,
new BoundExpression[] { operandVal, allBitsExpr }));
}
return returnValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: beb46a34e718df243927180e735d4c2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,34 @@

using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal class BoundBlock : BoundStatement
{
private ImmutableArray<BoundStatement> Statements { get; }
public BoundBlock(SyntaxNode node)
:base(node)
{
Statements = ImmutableArray<BoundStatement>.Empty;
}
public BoundBlock(SyntaxNode node, IEnumerable<BoundStatement> statements)
: base(node)
{
Statements = ImmutableArray.CreateRange(statements);
}
public override void Emit(EmitContext context)
{
using (context.OpenBlockScope())
{
foreach (BoundStatement statement in Statements)
context.Emit(statement);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd541469feb075d4eacb3446c5039db8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundBreakStatement : BoundStatement
{
public BoundBreakStatement(SyntaxNode node)
:base(node)
{
}
public override void Emit(EmitContext context)
{
context.Module.AddJump(context.TopBreakLabel);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29000ebe49b8b1542b77829cce1b5323
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,32 @@

using System;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundCastExpression : BoundExpression
{
private TypeSymbol TargetType { get; }
private bool IsExplicit { get; }
public override TypeSymbol ValueType => TargetType;
public BoundCastExpression(SyntaxNode node, BoundExpression sourceExpression, TypeSymbol targetType, bool isExplicit)
: base(node, sourceExpression)
{
if (targetType is TypeParameterSymbol)
throw new InvalidOperationException("Cannot cast to generic parameter types");
TargetType = targetType;
IsExplicit = isExplicit;
}
public override Value EmitValue(EmitContext context)
{
return context.CastValue(context.EmitValue(SourceExpression), TargetType, IsExplicit);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2b4a317dc7fb76a40b198a313d5a936b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,54 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundCoalesceExpression : BoundExpression
{
private BoundExpression Lhs { get; }
private BoundExpression Rhs { get; }
public BoundCoalesceExpression(SyntaxNode node, BoundExpression lhs, BoundExpression rhs)
: base(node, null)
{
Lhs = lhs;
Rhs = rhs;
}
public override TypeSymbol ValueType => Lhs.ValueType;
public override Value EmitValue(EmitContext context)
{
// We don't want any references outside the flow control to be dirtied conditionally
context.TopTable.DirtyAllValues();
Value returnValue = context.GetReturnValue(ValueType);
context.EmitValueAssignment(returnValue, Lhs);
TypeSymbol systemObjectType = context.GetTypeSymbol(SpecialType.System_Object);
MethodSymbol objectEquality = new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Equality, systemObjectType, context);
Value conditionCheck = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(context, null,
objectEquality, null,
new BoundExpression[]
{
BoundAccessExpression.BindAccess(returnValue),
BoundAccessExpression.BindAccess(context.GetConstantValue(systemObjectType, null))
}));
JumpLabel notNullLabel = context.Module.CreateLabel();
context.Module.AddJumpIfFalse(notNullLabel, conditionCheck);
context.EmitValueAssignment(returnValue, Rhs);
context.Module.LabelJump(notNullLabel);
return returnValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 71ebbbf1dd6f1e844a417f1b6b504d7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,54 @@

using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundConditionalExpression : BoundExpression
{
private BoundExpression ConditionExpression { get; }
private BoundExpression TrueExpression { get; }
private BoundExpression FalseExpression { get; }
public override TypeSymbol ValueType { get; }
public BoundConditionalExpression(ConditionalExpressionSyntax node, TypeSymbol resultType, BoundExpression conditionExpression, BoundExpression trueExpression, BoundExpression falseExpression)
: base(node)
{
ConditionExpression = conditionExpression;
TrueExpression = trueExpression;
FalseExpression = falseExpression;
ValueType = resultType;
}
public override Value EmitValue(EmitContext context)
{
var assignmentInterrupt = context.InterruptAssignmentScope();
Value conditionValue = context.EmitValue(ConditionExpression);
assignmentInterrupt.Dispose();
JumpLabel conditionJump = context.Module.CreateLabel();
JumpLabel exitTrueJump = context.Module.CreateLabel();
Value returnValue = context.GetReturnValue(ValueType);
// We don't want any references outside the flow control to be dirtied conditionally
context.TopTable.DirtyAllValues();
context.Module.AddJumpIfFalse(conditionJump, conditionValue);
context.EmitValueAssignment(returnValue, TrueExpression);
context.Module.AddJump(exitTrueJump);
context.Module.LabelJump(conditionJump);
context.EmitValueAssignment(returnValue, FalseExpression);
context.Module.LabelJump(exitTrueJump);
return returnValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d00b34c212929b46ab3530fd6f6bd15
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@

using System;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal class BoundConstArrayCreationExpression : BoundExpression
{
private TypeSymbol ArrayType { get; }
private TypeSymbol ElementType { get; }
private BoundExpression[] Initializers { get; }
public BoundConstArrayCreationExpression(SyntaxNode node, TypeSymbol arrayType, BoundExpression[] initializers)
: base(node)
{
ArrayType = arrayType;
ElementType = arrayType.ElementType;
Initializers = initializers;
}
public override TypeSymbol ValueType => ArrayType;
public override Value EmitValue(EmitContext context)
{
Value returnValue;
if (context.IsRecursiveMethodEmit)
{
using (context.InterruptAssignmentScope())
{
returnValue = context.EmitValue(new BoundArrayCreationExpression(SyntaxNode, context, ArrayType,
new BoundExpression[]
{
BoundAccessExpression.BindAccess(
context.GetConstantValue(context.GetTypeSymbol(SpecialType.System_Int32),
Initializers.Length))
}, null));
}
}
else
{
returnValue = context.CreateGlobalInternalValue(ArrayType);
returnValue.DefaultValue = Activator.CreateInstance(ArrayType.UdonType.SystemType, Initializers.Length);
}
BoundAccessExpression arrayAccess = BoundAccessExpression.BindAccess(returnValue);
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
// This is quite wasteful for allocations in the compile, todo: look at caching these safely
for (int i = 0; i < Initializers.Length; ++i)
{
BoundAccessExpression elementAccess = BoundAccessExpression.BindElementAccess(context, SyntaxNode, arrayAccess, new BoundExpression[]{new BoundConstantExpression(new ConstantValue<int>(i), intType, SyntaxNode)});
context.EmitSet(elementAccess, Initializers[i]);
}
return returnValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 14ff91ebda983c347a578b557526495e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,18 @@
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundContinueStatement : BoundStatement
{
public BoundContinueStatement(SyntaxNode node)
:base(node)
{
}
public override void Emit(EmitContext context)
{
context.Module.AddJump(context.TopContinueLabel);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97d2bf6e5ad2cfd4ca0c07bc65e0722e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundDoStatement : BoundStatement
{
private BoundExpression Condition { get; }
private BoundStatement Body { get; }
public BoundDoStatement(SyntaxNode node, BoundExpression condition, BoundStatement body)
:base(node)
{
Condition = condition;
Body = body;
}
public override void Emit(EmitContext context)
{
JumpLabel continueLabel = context.PushContinueLabel();
JumpLabel breakLabel = context.PushBreakLabel();
context.Module.LabelJump(continueLabel);
context.Emit(Body);
context.Module.AddJumpIfFalse(breakLabel, context.EmitValue(Condition));
context.Module.AddJump(continueLabel);
context.Module.LabelJump(breakLabel);
context.PopBreakLabel();
context.PopContinueLabel();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac98576983713f547ac803a0b22bc2eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,214 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
using UdonSharp.Core;
using UdonSharp.Localization;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundExpression : BoundNode
{
public bool IsConstant => ConstantValue != null;
public virtual IConstantValue ConstantValue => null;
/// <summary>
/// The user type of Value that this expression will emit when EmitValue is called
/// </summary>
public abstract TypeSymbol ValueType { get; }
public bool IsThis { get; protected set; }
protected BoundExpression SourceExpression { get; }
protected BoundExpression(SyntaxNode node, BoundExpression sourceExpression = null)
: base(node)
{
SourceExpression = sourceExpression;
}
/// <summary>
/// All expressions must instead implement EmitValue since they will always evaluate to something
/// </summary>
/// <param name="context"></param>
public override void Emit(EmitContext context)
{
context.EmitValue(this);
}
public abstract Value EmitValue(EmitContext context);
protected virtual void ReleaseCowValuesImpl(EmitContext context) {}
public void ReleaseCowReferences(EmitContext context)
{
ReleaseCowValuesImpl(context);
context.ReleaseCowValues(this);
SourceExpression?.ReleaseCowReferences(context);
}
protected ExternFieldSymbol SetupExternAccessor(SyntaxNode node, AbstractPhaseContext context, ExternFieldSymbol externFieldAccessor,
BoundExpression sourceExpression, CompilerUdonInterface.FieldAccessorType accessorType,
Func<TypeSymbol, ExternFieldSymbol> synthesizedFieldSymbolFactory)
{
string sig = accessorType == CompilerUdonInterface.FieldAccessorType.Get ? externFieldAccessor.ExternGetSignature : externFieldAccessor.ExternSetSignature;
if (!CompilerUdonInterface.IsExposedToUdon(sig))
{
ExternFieldSymbol externAlternateAccessor = FindAlternateAccessor(context, externFieldAccessor, sourceExpression, accessorType, synthesizedFieldSymbolFactory);
if (externAlternateAccessor == null)
{
throw new NotExposedException(LocStr.CE_UdonFieldNotExposed, node, $"{externFieldAccessor.RoslynSymbol?.ToDisplayString() ?? externFieldAccessor.ToString()}, sig: {sig}");
}
else
{
return externAlternateAccessor;
}
}
return externFieldAccessor;
}
protected ExternMethodSymbol SetupExternMethodSymbol(SyntaxNode node, AbstractPhaseContext context, ExternMethodSymbol methodSymbol,
BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
{
if (!CompilerUdonInterface.IsExposedToUdon(methodSymbol.ExternSignature))
{
ExternMethodSymbol externAlternateMethodSymbol = FindAlternateInvocation(context, methodSymbol, instanceExpression, parameterExpressions);
if (externAlternateMethodSymbol == null)
{
throw new NotExposedException(LocStr.CE_UdonMethodNotExposed, node, $"{methodSymbol.RoslynSymbol?.ToDisplayString() ?? methodSymbol.ToString()}, sig: {methodSymbol.ExternSignature}");
}
else
{
return externAlternateMethodSymbol;
}
}
return methodSymbol;
}
private static ExternFieldSymbol FindAlternateAccessor(AbstractPhaseContext context, ExternFieldSymbol originalFieldSymbol,
BoundExpression sourceExpression, CompilerUdonInterface.FieldAccessorType accessorType,
Func<TypeSymbol, ExternFieldSymbol> synthesizedFieldSymbolFactory)
{
if (originalFieldSymbol.IsStatic) return null;
List<TypeSymbol> candidates = new List<TypeSymbol>();
FindCandidateAlternateTypes(context, candidates, sourceExpression?.ValueType ?? originalFieldSymbol.ContainingType);
foreach (TypeSymbol candidate in candidates)
{
ExternFieldSymbol externFieldSymbol = synthesizedFieldSymbolFactory(candidate);
string sig = accessorType == CompilerUdonInterface.FieldAccessorType.Get ? externFieldSymbol.ExternGetSignature : externFieldSymbol.ExternSetSignature;
if (CompilerUdonInterface.IsExposedToUdon(sig))
{
return externFieldSymbol;
}
}
return null;
}
private static ExternMethodSymbol FindAlternateInvocation(AbstractPhaseContext context,
MethodSymbol originalSymbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
{
if (originalSymbol.IsStatic || originalSymbol.IsConstructor) return null;
List<TypeSymbol> candidates = new List<TypeSymbol>();
FindCandidateAlternateTypes(context, candidates, instanceExpression.ValueType);
TypeSymbol[] paramTypes = parameterExpressions.Select(ex => ex.ValueType).ToArray();
foreach (TypeSymbol candidate in candidates)
{
ExternMethodSymbol externMethodSymbol = new ExternSynthesizedMethodSymbol(context, originalSymbol.Name, candidate, paramTypes, originalSymbol.ReturnType, false, false);
if (CompilerUdonInterface.IsExposedToUdon(externMethodSymbol.ExternSignature))
{
return externMethodSymbol;
}
}
return null;
}
private static void FindCandidateAlternateTypes(AbstractPhaseContext context, List<TypeSymbol> candidates, TypeSymbol ty)
{
foreach (var intf in ty.RoslynSymbol.AllInterfaces)
{
candidates.Add(context.GetTypeSymbol(intf));
}
while (ty != null)
{
candidates.Add(ty);
ty = ty.BaseType;
}
}
}
internal class BoundConstantExpression : BoundAccessExpression
{
public override IConstantValue ConstantValue { get; }
public TypeSymbol ConstantType { get; }
public override TypeSymbol ValueType => ConstantType;
public BoundConstantExpression(IConstantValue constantValue, TypeSymbol constantType, SyntaxNode node)
:base(node, null)
{
ConstantValue = constantValue;
ConstantType = constantType;
}
public BoundConstantExpression(object constantValue, TypeSymbol typeSymbol)
:base(null, null)
{
ConstantType = typeSymbol;
Type targetType = typeSymbol.UdonType.SystemType;
if (typeSymbol.IsEnum && typeSymbol.IsExtern)
constantValue = Enum.ToObject(targetType, constantValue);
ConstantValue =
(IConstantValue) Activator.CreateInstance(typeof(ConstantValue<>).MakeGenericType(typeSymbol.UdonType.SystemType),
constantValue);
}
public BoundConstantExpression(object constantValue, TypeSymbol typeSymbol, SyntaxNode node)
:base(null, null)
{
ConstantType = typeSymbol;
Type targetType = typeSymbol.UdonType.SystemType;
if (typeSymbol.IsEnum && typeSymbol.IsExtern)
constantValue = Enum.ToObject(targetType, constantValue);
ConstantValue =
(IConstantValue) Activator.CreateInstance(typeof(ConstantValue<>).MakeGenericType(typeSymbol.UdonType.SystemType),
constantValue);
}
public override Value EmitValue(EmitContext context)
{
return context.GetConstantValue(ConstantType, ConstantValue.Value);
}
public override string ToString()
{
return $"BoundConstantExpression<{ConstantValue.GetType().GetGenericArguments()[0]}>: " + ConstantValue.Value?.ToString() ?? "null";
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
throw new InvalidOperationException("Cannot set value on a constant value");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1dbf0ab7fd74e2649b46c6685f097515
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal class BoundExpressionStatement : BoundStatement
{
public BoundExpression Expression { get; }
public BoundExpressionStatement(SyntaxNode node, BoundExpression expression)
: base(node)
{
Expression = expression;
}
public override void Emit(EmitContext context)
{
context.Emit(Expression);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2bde224ee87b1b34a96be7c62bea99d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,102 @@

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal class BoundExternInvocation : BoundInvocationExpression
{
private ExternMethodSymbol externMethodSymbol;
public BoundExternInvocation(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
: base(node, method, instanceExpression, parameterExpressions)
{
externMethodSymbol = SetupExternMethodSymbol(node, context, (ExternMethodSymbol)method, instanceExpression, parameterExpressions);
}
public override Value EmitValue(EmitContext context)
{
var module = context.Module;
Value.CowValue instanceValue = null;
using (context.InterruptAssignmentScope())
{
if (!Method.IsStatic && !Method.IsConstructor)
{
instanceValue = GetInstanceValue(context);
// Prevent mutating a value constant if you call a method on the constant for some reason
// todo: fix for use with the cowvalues
// if (instanceValue.IsConstant && instanceValue.UserType.IsValueType &&
// Method.Name != "ToString" && Method.Name != "Equals") // Common non-mutating methods
// {
// Value proxyInstanceValue = context.CreateInternalValue(instanceValue.UserType);
// module.AddCopy(instanceValue, proxyInstanceValue);
// instanceValue = proxyInstanceValue;
// }
}
}
Value[] parameterValues = GetParameterValues(context);
if (instanceValue != null)
module.AddPush(instanceValue.Value);
Value[] recursiveValues = null;
string methodName = Method.Name;
if (methodName == "SendCustomEvent" ||
methodName == "SendCustomNetworkEvent" ||
methodName == "RunProgram")
{
if (context.IsRecursiveMethodEmit)
{
recursiveValues = context.CollectRecursiveValues();
CheckStackSize(context.GetConstantValue(context.GetTypeSymbol(SpecialType.System_Int32), recursiveValues.Length), context);
PushRecursiveValues(recursiveValues, context);
context.UpdateRecursiveStackMaxSize(recursiveValues.Length);
}
else
{
instanceValue?.Dispose();
}
context.TopTable.DirtyAllValues();
}
foreach (Value value in parameterValues)
module.AddPush(value);
Value returnValue = null;
if (IsPropertySetter)
returnValue = parameterValues.Last();
if (Method.ReturnType != null)
{
returnValue = context.GetReturnValue(Method.ReturnType);
if (returnValue.IsConstant)
throw new InvalidOperationException("Cannot return to const value");
module.AddPush(returnValue);
}
module.AddExtern(externMethodSymbol);
if (recursiveValues != null)
PopRecursiveValues(recursiveValues, context);
return returnValue;
}
public override string ToString()
{
return $"BoundExternInvocation: {Method}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 004d86898762cfa49a0d4341af07d3be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,176 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
using UdonSharp.Core;
using UdonSharp.Localization;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundFieldAccessExpression : BoundAccessExpression
{
protected BoundFieldAccessExpression(SyntaxNode node, BoundExpression sourceExpression)
: base(node, sourceExpression)
{
}
public static BoundAccessExpression BindFieldAccess(AbstractPhaseContext context, SyntaxNode node, FieldSymbol fieldSymbol, BoundExpression sourceExpression)
{
if (fieldSymbol.IsConst)
return new BoundConstantExpression(fieldSymbol.RoslynSymbol.ConstantValue, fieldSymbol.Type);
if (fieldSymbol is ExternFieldSymbol externField)
{
return new BoundExternFieldAccessExpression(node, context, externField, sourceExpression);
}
if (fieldSymbol is UdonSharpBehaviourFieldSymbol udonSharpBehaviourFieldSymbol)
return new BoundUdonSharpBehaviourFieldAccessExpression(node, udonSharpBehaviourFieldSymbol,
sourceExpression);
throw new NotImplementedException($"Field access for symbol {fieldSymbol.GetType()} is not implemented");
}
private sealed class BoundExternFieldAccessExpression : BoundFieldAccessExpression
{
private ExternFieldSymbol Field { get; }
public override TypeSymbol ValueType => Field.Type;
public BoundExternFieldAccessExpression(SyntaxNode node, AbstractPhaseContext context, ExternFieldSymbol field,
BoundExpression sourceExpression)
: base(node, sourceExpression)
{
// This is access, so check for get only
// Passing originalSymbol.RoslynSymbol to synthesized fields to inherit attributes (static, const, etc.)
Field = SetupExternAccessor(node, context, field, sourceExpression, CompilerUdonInterface.FieldAccessorType.Get,
(candidate) => new ExternSynthesizedFieldSymbol(field.RoslynSymbol, context, field.Name, candidate, field.Type));
}
private Value.CowValue GetInstanceValue(EmitContext context)
{
Value.CowValue[] instanceValue = context.GetExpressionCowValues(this, "propertyInstance");
if (instanceValue != null)
return instanceValue[0];
Value.CowValue instanceCowValue;
using (context.InterruptAssignmentScope())
instanceCowValue = context.EmitValueWithDeferredRelease(SourceExpression).GetCowValue(context);
context.RegisterCowValues(new []{instanceCowValue}, this, "propertyInstance");
return instanceCowValue;
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
Value.CowValue sourceValue = null;
if (!Field.IsStatic)
sourceValue = GetInstanceValue(context);
Value expressionResult = context.EmitValue(valueExpression);
if (sourceValue != null)
context.Module.AddPush(sourceValue.Value);
context.Module.AddPush(expressionResult);
context.Module.AddExternSet(Field);
if (sourceValue != null)
{
if (SourceExpression.ValueType.IsValueType &&
SourceExpression is BoundAccessExpression sourceAccessExpression)
context.EmitSet(sourceAccessExpression, BindAccess(sourceValue.Value));
}
return expressionResult;
}
public override Value EmitValue(EmitContext context)
{
Value returnValue = context.GetReturnValue(Field.Type);
Value.CowValue sourceValue = null;
if (!Field.IsStatic)
sourceValue = GetInstanceValue(context);
if (sourceValue != null)
context.Module.AddPush(sourceValue.Value);
context.Module.AddPush(returnValue);
context.Module.AddExternGet(Field);
return returnValue;
}
}
private sealed class BoundUdonSharpBehaviourFieldAccessExpression : BoundFieldAccessExpression
{
private FieldSymbol Field { get; }
public BoundUdonSharpBehaviourFieldAccessExpression(SyntaxNode node, FieldSymbol fieldSymbol, BoundExpression sourceExpression)
: base(node, sourceExpression)
{
Field = fieldSymbol;
}
public override TypeSymbol ValueType => Field.Type;
public override Value EmitValue(EmitContext context)
{
if (SourceExpression == null || SourceExpression.IsThis)
return context.GetUserValue(Field);
TypeSymbol stringType = context.GetTypeSymbol(SpecialType.System_String);
MethodSymbol setProgramVariableMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour))
.GetMembers<MethodSymbol>("GetProgramVariable", context)
.First(e => e.Parameters.Length == 1 &&
e.Parameters[0].Type == stringType);
return context.CastValue(context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
setProgramVariableMethod, SourceExpression,
new BoundExpression[]
{
BindAccess(context.GetConstantValue(stringType, Field.Name))
})), Field.Type, true);
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
if (SourceExpression == null || SourceExpression.IsThis)
return context.EmitValueAssignment(context.GetUserValue(Field), valueExpression);
if (Field.HasAttribute<FieldChangeCallbackAttribute>())
throw new CompilerException("Cannot set field on U# behaviour by reference when that field has a FieldChangeCallback attribute.");
TypeSymbol stringType = context.GetTypeSymbol(SpecialType.System_String);
MethodSymbol setProgramVariableMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour))
.GetMembers<MethodSymbol>("SetProgramVariable", context)
.First(e => e.Parameters.Length == 2 &&
e.Parameters[0].Type == stringType);
Value value = context.EmitValue(valueExpression);
context.Emit(BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
setProgramVariableMethod, SourceExpression,
new BoundExpression[]
{
BindAccess(context.GetConstantValue(stringType, Field.Name)),
BindAccess(value)
}));
return value;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3bd7d982e8955b547b222c05bf89facc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,90 @@

using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundForEachCharStatement : BoundStatement
{
private BoundExpression IteratorSource { get; }
private Symbol ValueSymbol { get; }
private BoundStatement BodyStatement { get; }
public BoundForEachCharStatement(SyntaxNode node, BoundExpression iteratorSource, Symbol valueSymbol, BoundStatement bodyStatement)
:base(node)
{
IteratorSource = iteratorSource;
ValueSymbol = valueSymbol;
BodyStatement = bodyStatement;
}
public override void Emit(EmitContext context)
{
var blockScope = context.OpenBlockScope();
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
MethodSymbol toCharArrayMethod = context.GetTypeSymbol(SpecialType.System_String).GetMembers<MethodSymbol>("ToCharArray", context).First(e => e.Parameters.Length == 0);
PropertySymbol lengthProperty = context.GetTypeSymbol(SpecialType.System_Array).GetMember<PropertySymbol>("Length", context);
Value iteratorValue = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode, toCharArrayMethod, IteratorSource, new BoundExpression[] {}));
iteratorValue.MarkUsedRecursively();
var iteratorAccess = BoundAccessExpression.BindAccess(iteratorValue);
Value arraySize = context.CreateInternalValue(intType);
arraySize.MarkUsedRecursively();
BoundAccessExpression getLength = BoundAccessExpression.BindAccess(context, SyntaxNode, lengthProperty, iteratorAccess);
context.EmitValueAssignment(arraySize, getLength);
// Declare and reset incrementor value
Value incrementorValue = context.CreateInternalValue(intType);
incrementorValue.MarkUsedRecursively();
context.EmitValueAssignment(incrementorValue, BoundAccessExpression.BindAccess(context.GetConstantValue(intType, 0)));
JumpLabel loopLabel = context.Module.CreateLabel();
context.Module.LabelJump(loopLabel);
var incrementorAccess = BoundAccessExpression.BindAccess(incrementorValue);
BoundExpression increment = new BoundInvocationExpression.BoundPrefixOperatorExpression(context, SyntaxNode,
incrementorAccess, new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Addition, intType, context));
var lengthCheck = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.LessThan, intType, context), null,
new BoundExpression[]
{
incrementorAccess,
BoundAccessExpression.BindAccess(arraySize)
});
JumpLabel exitLoopLabel = context.PushBreakLabel();
JumpLabel continueLabel = context.PushContinueLabel();
Value lengthCheckResult = context.EmitValue(lengthCheck);
context.Module.AddJumpIfFalse(exitLoopLabel, lengthCheckResult);
context.EmitValueAssignment(context.GetUserValue(ValueSymbol),
BoundAccessExpression.BindElementAccess(context, SyntaxNode, iteratorAccess,
new BoundExpression[] {incrementorAccess}));
context.Emit(BodyStatement);
context.Module.LabelJump(continueLabel);
context.Emit(increment);
context.Module.AddJump(loopLabel);
context.Module.LabelJump(exitLoopLabel);
context.PopBreakLabel();
context.PopContinueLabel();
blockScope.Dispose();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 471eaf7f712b1b94b8729a6db30302a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,91 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UnityEngine;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundForEachChildTransformStatement : BoundStatement
{
private BoundExpression IteratorSource { get; }
private Symbol ValueSymbol { get; }
private BoundStatement BodyStatement { get; }
public BoundForEachChildTransformStatement(SyntaxNode node, BoundExpression iteratorSource, Symbol valueSymbol, BoundStatement bodyStatement)
:base(node)
{
IteratorSource = iteratorSource;
ValueSymbol = valueSymbol;
BodyStatement = bodyStatement;
}
public override void Emit(EmitContext context)
{
var blockScope = context.OpenBlockScope();
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
TypeSymbol transformType = context.GetTypeSymbol(typeof(Transform));
MethodSymbol getChildMethod = transformType.GetMember<MethodSymbol>("GetChild", context);
PropertySymbol lengthProperty = transformType.GetMember<PropertySymbol>("childCount", context);
Value iteratorValue = context.EmitValue(IteratorSource);
iteratorValue.MarkUsedRecursively();
var iteratorAccess = BoundAccessExpression.BindAccess(iteratorValue);
Value arraySize = context.CreateInternalValue(intType);
arraySize.MarkUsedRecursively();
BoundAccessExpression getLength = BoundAccessExpression.BindAccess(context, SyntaxNode, lengthProperty, iteratorAccess);
context.EmitValueAssignment(arraySize, getLength);
// Declare and reset incrementor value
Value incrementorValue = context.CreateInternalValue(intType);
incrementorValue.MarkUsedRecursively();
context.EmitValueAssignment(incrementorValue, BoundAccessExpression.BindAccess(context.GetConstantValue(intType, 0)));
JumpLabel loopLabel = context.Module.CreateLabel();
context.Module.LabelJump(loopLabel);
var incrementorAccess = BoundAccessExpression.BindAccess(incrementorValue);
BoundExpression increment = new BoundInvocationExpression.BoundPrefixOperatorExpression(context, SyntaxNode,
incrementorAccess, new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Addition, intType, context));
var lengthCheck = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.LessThan, intType, context), null,
new BoundExpression[]
{
incrementorAccess,
BoundAccessExpression.BindAccess(arraySize)
});
JumpLabel exitLoopLabel = context.PushBreakLabel();
JumpLabel continueLabel = context.PushContinueLabel();
Value lengthCheckResult = context.EmitValue(lengthCheck);
context.Module.AddJumpIfFalse(exitLoopLabel, lengthCheckResult);
context.EmitValueAssignment(context.GetUserValue(ValueSymbol),
BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode, getChildMethod, iteratorAccess,
new BoundExpression[] {incrementorAccess}));
context.Emit(BodyStatement);
context.Module.LabelJump(continueLabel);
context.Emit(increment);
context.Module.AddJump(loopLabel);
context.Module.LabelJump(exitLoopLabel);
context.PopBreakLabel();
context.PopContinueLabel();
blockScope.Dispose();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 87631ba1def01394daff5d58136487a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundForEachStatement : BoundStatement
{
private BoundExpression IteratorSource { get; }
private Symbol ValueSymbol { get; }
private BoundStatement BodyStatement { get; }
public BoundForEachStatement(SyntaxNode node, BoundExpression iteratorSource, Symbol valueSymbol, BoundStatement bodyStatement)
:base(node)
{
IteratorSource = iteratorSource;
ValueSymbol = valueSymbol;
BodyStatement = bodyStatement;
}
public override void Emit(EmitContext context)
{
var blockScope = context.OpenBlockScope();
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
Value iteratorValue = context.EmitValue(IteratorSource);
iteratorValue.MarkUsedRecursively();
var iteratorAccess = BoundAccessExpression.BindAccess(iteratorValue);
Value arraySize = context.CreateInternalValue(intType);
arraySize.MarkUsedRecursively();
PropertySymbol lengthProperty = context.GetTypeSymbol(SpecialType.System_Array).GetMember<PropertySymbol>("Length", context);
BoundAccessExpression getLength = BoundAccessExpression.BindAccess(context, SyntaxNode, lengthProperty, iteratorAccess);
context.EmitValueAssignment(arraySize, getLength);
// Declare and reset incrementor value
Value incrementorValue = context.CreateInternalValue(intType);
incrementorValue.MarkUsedRecursively();
context.EmitValueAssignment(incrementorValue, BoundAccessExpression.BindAccess(context.GetConstantValue(intType, 0)));
JumpLabel loopLabel = context.Module.CreateLabel();
context.Module.LabelJump(loopLabel);
var incrementorAccess = BoundAccessExpression.BindAccess(incrementorValue);
BoundExpression increment = new BoundInvocationExpression.BoundPrefixOperatorExpression(context, SyntaxNode,
incrementorAccess, new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Addition, intType, context));
BoundExpression lengthCheck = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.LessThan, intType, context), null,
new BoundExpression[]
{
incrementorAccess,
BoundAccessExpression.BindAccess(arraySize)
});
JumpLabel exitLoopLabel = context.PushBreakLabel();
JumpLabel continueLabel = context.PushContinueLabel();
Value lengthCheckResult = context.EmitValue(lengthCheck);
context.Module.AddJumpIfFalse(exitLoopLabel, lengthCheckResult);
context.EmitValueAssignment(context.GetUserValue(ValueSymbol),
BoundAccessExpression.BindElementAccess(context, SyntaxNode, iteratorAccess,
new BoundExpression[] {incrementorAccess}));
context.Emit(BodyStatement);
context.Module.LabelJump(continueLabel);
context.Emit(increment);
context.Module.AddJump(loopLabel);
context.Module.LabelJump(exitLoopLabel);
context.PopBreakLabel();
context.PopContinueLabel();
blockScope.Dispose();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b07c77cc30beda43bcdb9851b8de6fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundForStatement : BoundStatement
{
private BoundVariableDeclarationStatement Declaration { get; }
private BoundExpression[] Initializers { get; }
private BoundExpression Condition { get; }
private BoundExpression[] Incrementors { get; }
private BoundStatement Body { get; }
public BoundForStatement(SyntaxNode node, BoundVariableDeclarationStatement declaration, BoundExpression[] initializers, BoundExpression condition, BoundExpression[] incrementors, BoundStatement body)
:base(node)
{
Declaration = declaration;
Initializers = initializers;
Condition = condition;
Incrementors = incrementors;
Body = body;
}
public override void Emit(EmitContext context)
{
using (context.OpenBlockScope())
{
if (Declaration != null)
context.Emit(Declaration);
foreach (var initializer in Initializers)
context.Emit(initializer);
JumpLabel continueLabel = context.PushContinueLabel();
JumpLabel breakLabel = context.PushBreakLabel();
JumpLabel loopLabel = context.Module.CreateLabel();
context.Module.LabelJump(loopLabel);
if (Condition != null)
context.Module.AddJumpIfFalse(breakLabel, context.EmitValue(Condition));
context.Emit(Body);
context.Module.LabelJump(continueLabel);
foreach (var incrementor in Incrementors)
context.Emit(incrementor);
context.Module.AddJump(loopLabel);
context.Module.LabelJump(breakLabel);
context.PopBreakLabel();
context.PopContinueLabel();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edb01f70cd2b7bd4ebce59dfe86e28aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundIfStatement : BoundStatement
{
private BoundExpression ConditionExpression { get; }
private BoundStatement BodyStatement { get; }
private BoundStatement ElseStatement { get; }
public BoundIfStatement(SyntaxNode node, BoundExpression conditionExpression, BoundStatement bodyStatement, BoundStatement elseStatement)
:base(node)
{
ConditionExpression = conditionExpression;
BodyStatement = bodyStatement;
ElseStatement = elseStatement;
}
public override void Emit(EmitContext context)
{
Value conditionValue = context.EmitValue(ConditionExpression);
JumpLabel exitLabel = context.Module.CreateLabel();
JumpLabel failLabel = context.Module.CreateLabel();
context.Module.AddJumpIfFalse(failLabel, conditionValue);
if (BodyStatement != null)
context.Emit(BodyStatement);
if (ElseStatement != null)
context.Module.AddJump(exitLabel);
context.Module.LabelJump(failLabel);
if (ElseStatement != null)
context.Emit(ElseStatement);
context.Module.LabelJump(exitLabel);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7bc238b740f2a564f954f381c12538cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,67 @@

using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundInterpolatedStringExpression : BoundExpression
{
private BoundConstantExpression BuiltStr { get; }
private BoundExpression[] InterpolationExpressions { get; }
private ExternMethodSymbol StringFormatMethod { get; }
public override TypeSymbol ValueType { get; }
private TypeSymbol ObjectArr { get; }
public BoundInterpolatedStringExpression(InterpolatedStringExpressionSyntax node, string builtStr, BoundExpression[] interpolatedExpressions, BindContext context)
: base(node)
{
ValueType = context.GetTypeSymbol(SpecialType.System_String);
BuiltStr = new BoundConstantExpression(new ConstantValue<string>(builtStr), ValueType, node);
InterpolationExpressions = interpolatedExpressions;
ObjectArr = context.GetTypeSymbol(SpecialType.System_Object).MakeArrayType(context);
if (interpolatedExpressions.Length > 3)
{
StringFormatMethod = ValueType.GetMembers<ExternMethodSymbol>("Format", context).First(e =>
e.Parameters[0].Type == ValueType && e.Parameters[1].Type == ObjectArr);
}
else
{
StringFormatMethod = ValueType.GetMembers<ExternMethodSymbol>("Format", context).First(e =>
e.Parameters[0].Type == ValueType && e.Parameters.Length == interpolatedExpressions.Length + 1);
}
}
public override Value EmitValue(EmitContext context)
{
BoundInvocationExpression formatInvoke;
if (InterpolationExpressions.Length > 3)
{
BoundConstArrayCreationExpression interpolationArray =
new BoundConstArrayCreationExpression(SyntaxNode, ObjectArr, InterpolationExpressions);
BoundAccessExpression arrayAccess =
BoundAccessExpression.BindAccess(context.EmitValue(interpolationArray));
formatInvoke = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode, StringFormatMethod,
null, new BoundExpression[] { BuiltStr, arrayAccess });
}
else
{
formatInvoke = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode, StringFormatMethod,
null, new BoundExpression[] {BuiltStr}.Concat(InterpolationExpressions).ToArray());
}
return context.EmitValue(formatInvoke);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: daea6d6d4a4f1f54e9b463cc39143799
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,855 @@

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Compiler.Udon;
using UdonSharp.Core;
using UdonSharp.Internal;
using UdonSharp.Lib.Internal;
using UdonSharp.Localization;
using UnityEngine;
using VRC.Udon;
using NotSupportedException = UdonSharp.Core.NotSupportedException;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundInvocationExpression : BoundExpression
{
[PublicAPI]
public MethodSymbol Method { get; }
[PublicAPI]
public BoundExpression[] ParameterExpressions { get; }
public override TypeSymbol ValueType => Method.ReturnType;
protected bool IsPropertySetter { get; private set; }
public void MarkPropertySetter()
{
IsPropertySetter = true;
}
protected BoundInvocationExpression(SyntaxNode node, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
:base(node, instanceExpression)
{
Method = method;
ParameterExpressions = parameterExpressions;
}
/// <summary>
/// Marks a bound invocation as a base invocation which prevents searching for more derived methods for the call and prevents a virtual call altogether
/// </summary>
public virtual void MarkForcedBaseCall() {}
private static readonly HashSet<string> _getComponentNames = new HashSet<string>()
{
"GetComponent",
"GetComponents",
"GetComponentInChildren",
"GetComponentsInChildren",
"GetComponentInParent",
"GetComponentsInParent",
};
private static bool TryCreateUdonSharpMetadataInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression,
out BoundInvocationExpression createdInvocation)
{
if (symbol.Name == "GetUdonTypeID" || symbol.Name == "GetUdonTypeName")
{
if (symbol.IsStatic &&
symbol.TypeArguments.Length == 1 &&
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour)))
{
IConstantValue constantValue;
TypeSymbol constantType;
var typeArgs = symbol.TypeArguments.Select(e => context.GetTypeSymbol(e.RoslynSymbol)).ToArray();
if (symbol.Name == "GetUdonTypeID")
{
constantValue = new ConstantValue<long>(UdonSharpInternalUtility.GetTypeID(TypeSymbol.GetFullTypeName(typeArgs[0].RoslynSymbol)));
constantType = context.GetTypeSymbol(SpecialType.System_Int64);
}
else
{
constantValue = new ConstantValue<string>(TypeSymbol.GetFullTypeName(typeArgs[0].RoslynSymbol));
constantType = context.GetTypeSymbol(SpecialType.System_String);
}
createdInvocation = new BoundConstantInvocationExpression(node, constantValue, constantType);
return true;
}
if (!symbol.IsStatic &&
instanceExpression != null &&
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour)))
{
TypeSymbol methodContainer = context.GetTypeSymbol(typeof(UdonSharpBehaviourMethods));
var shimMethod = methodContainer.GetMember<MethodSymbol>(symbol.Name, context);
context.MarkSymbolReferenced(shimMethod);
createdInvocation = CreateBoundInvocation(context, node, shimMethod, null, new [] {instanceExpression});
return true;
}
}
createdInvocation = null;
return false;
}
private static readonly HashSet<Type> _brokenGetComponentTypes = new HashSet<Type>()
{
typeof(VRC.SDKBase.VRC_AvatarPedestal), typeof(VRC.SDK3.Components.VRCAvatarPedestal),
typeof(VRC.SDKBase.VRC_Pickup), typeof(VRC.SDK3.Components.VRCPickup),
typeof(VRC.SDKBase.VRC_PortalMarker), typeof(VRC.SDK3.Components.VRCPortalMarker),
//typeof(VRC.SDKBase.VRC_MirrorReflection), typeof(VRC.SDK3.Components.VRCMirrorReflection),
typeof(VRC.SDKBase.VRCStation),typeof(VRC.SDK3.Components.VRCStation),
typeof(VRC.SDK3.Video.Components.VRCUnityVideoPlayer),
typeof(VRC.SDK3.Video.Components.AVPro.VRCAVProVideoPlayer),
typeof(VRC.SDK3.Video.Components.Base.BaseVRCVideoPlayer),
typeof(VRC.SDK3.Components.VRCObjectPool),
typeof(VRC.SDK3.Components.VRCObjectSync),
typeof(UdonBehaviour),
};
private static bool TryCreateGetComponentInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if (symbol.RoslynSymbol != null &&
symbol.RoslynSymbol.IsGenericMethod &&
symbol.TypeArguments.Length == 1 &&
_getComponentNames.Contains(symbol.Name) &&
(symbol.ContainingType.UdonType.SystemType == typeof(Component) || symbol.ContainingType.UdonType.SystemType == typeof(GameObject)))
{
TypeSymbol gameObjectType = context.GetTypeSymbol(typeof(GameObject));
TypeSymbol typeArgument = symbol.TypeArguments[0];
// udon-workaround: Work around the udon bug where it checks the strongbox type instead of variable type and blows up when the strong box is `object`
if (instanceExpression.ValueType == gameObjectType)
{
PropertySymbol accessProperty = gameObjectType.GetMember<PropertySymbol>("transform", context);
instanceExpression = BoundAccessExpression.BindAccess(context, node, accessProperty, instanceExpression);
}
else
{
PropertySymbol accessProperty = context.GetTypeSymbol(typeof(Component)).GetMember<PropertySymbol>("transform", context);
instanceExpression = BoundAccessExpression.BindAccess(context, node, accessProperty, instanceExpression);
}
TypeSymbol udonSharpBehaviourType = context.GetTypeSymbol(typeof(UdonSharpBehaviour));
// Exact UdonSharpBehaviour type match
if (typeArgument == udonSharpBehaviourType)
{
MethodSymbol getComponentMethodShim = context.GetTypeSymbol(typeof(GetComponentShim))
.GetMembers<MethodSymbol>(symbol.Name + "USB", context)
.First(e => e.Parameters.Length == parameterExpressions.Length + 1);
createdInvocation = new BoundStaticUserMethodInvocation(node, getComponentMethodShim,
new [] {instanceExpression}.Concat(parameterExpressions).ToArray());
context.MarkSymbolReferenced(getComponentMethodShim);
return true;
}
// Subclass of UdonSharpBehaviour
if (typeArgument.IsUdonSharpBehaviour)
{
// Handle inherited types
if (context.CompileContext.HasInheritedUdonSharpBehaviours(typeArgument))
{
MethodSymbol getComponentInheritedMethodShim = context.GetTypeSymbol(typeof(GetComponentShim))
.GetMembers<MethodSymbol>(symbol.Name + "I", context)
.First(e => e.Parameters.Length == parameterExpressions.Length + 1);
getComponentInheritedMethodShim = getComponentInheritedMethodShim.ConstructGenericMethod(context, new [] { typeArgument });
createdInvocation = new BoundStaticUserMethodInvocation(node, getComponentInheritedMethodShim,
new [] {instanceExpression}.Concat(parameterExpressions).ToArray());
context.MarkSymbolReferenced(getComponentInheritedMethodShim);
return true;
}
MethodSymbol getComponentMethodShim = context.GetTypeSymbol(typeof(GetComponentShim))
.GetMembers<MethodSymbol>(symbol.Name, context)
.First(e => e.Parameters.Length == parameterExpressions.Length + 1);
getComponentMethodShim = getComponentMethodShim.ConstructGenericMethod(context, new [] { typeArgument });
createdInvocation = new BoundStaticUserMethodInvocation(node, getComponentMethodShim,
new [] {instanceExpression}.Concat(parameterExpressions).ToArray());
context.MarkSymbolReferenced(getComponentMethodShim);
return true;
}
if (_brokenGetComponentTypes.Contains(typeArgument.UdonType.SystemType))
{
MethodSymbol getComponentInheritedMethodShim = context.GetTypeSymbol(typeof(GetComponentShim))
.GetMembers<MethodSymbol>(symbol.Name + "VRC", context)
.First(e => e.Parameters.Length == parameterExpressions.Length + 1);
getComponentInheritedMethodShim = getComponentInheritedMethodShim.ConstructGenericMethod(context, new [] { typeArgument });
createdInvocation = new BoundStaticUserMethodInvocation(node, getComponentInheritedMethodShim,
new [] {instanceExpression}.Concat(parameterExpressions).ToArray());
context.MarkSymbolReferenced(getComponentInheritedMethodShim);
return true;
}
createdInvocation = new BoundGetUnityEngineComponentInvocation(context, node, symbol,
instanceExpression,
parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateInstantiationInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
switch (symbol.Name)
{
case "Instantiate_Extern" when symbol.ContainingType == context.GetTypeSymbol(typeof(InstantiationShim)):
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context,
"VRCInstantiate.__Instantiate__UnityEngineGameObject__UnityEngineGameObject",
parameterExpressions.Select(e => e.ValueType).ToArray(),
context.GetTypeSymbol(typeof(GameObject)), true),
instanceExpression, parameterExpressions);
return true;
case "VRCInstantiate" when symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour)): // Backwards compatibility for UdonSharpBehaviour.VRCInstantiate
case "Instantiate" when symbol.ContainingType == context.GetTypeSymbol(typeof(UnityEngine.Object)):
{
if (symbol.Name != "VRCInstantiate" &&
(symbol.TypeArguments.Length != 1 ||
symbol.TypeArguments[0] != context.GetTypeSymbol(typeof(GameObject))))
throw new NotSupportedException("Udon does not support instantiating non-GameObject types");
TypeSymbol instantiateShim = context.GetTypeSymbol(typeof(InstantiationShim));
MethodSymbol instantiateMethod = instantiateShim.GetMembers<MethodSymbol>("Instantiate", context)
.First(e => e.Parameters
.Select(p => p.Type)
.SequenceEqual(parameterExpressions
.Select(p => p.ValueType)));
context.MarkSymbolReferenced(instantiateMethod);
createdInvocation = new BoundStaticUserMethodInvocation(node, instantiateMethod, parameterExpressions);
return true;
}
}
createdInvocation = null;
return false;
}
/// <summary>
/// Udon exposes a generic SetProgramVariable which the overload finding will attempt to use and fail to find,
/// so just use the non-generic version in this case
/// </summary>
private static bool TryCreateSetProgramVariableInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if (symbol.Name == "SetProgramVariable" &&
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonBehaviour)))
{
MethodSymbol setProgramVarObjMethod = context.GetTypeSymbol(typeof(UdonBehaviour))
.GetMembers<MethodSymbol>("SetProgramVariable", context)
.First(e => !e.RoslynSymbol.IsGenericMethod);
createdInvocation = new BoundExternInvocation(node, context, setProgramVarObjMethod, instanceExpression,
parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateArrayMethodInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if ((symbol.Name == "IndexOf" || symbol.Name == "BinarySearch" || symbol.Name == "LastIndexOf" || symbol.Name == "Reverse") &&
symbol.ContainingType == context.GetTypeSymbol(typeof(Array)))
{
MethodSymbol arrayMethod = context.GetTypeSymbol(typeof(Array))
.GetMembers<MethodSymbol>(symbol.Name, context)
.First(e => !e.RoslynSymbol.IsGenericMethod && e.Parameters.Length == symbol.Parameters.Length);
createdInvocation = new BoundExternInvocation(node, context, arrayMethod, instanceExpression,
parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateTMPMethodInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if (symbol.ContainingType != null &&
symbol.ContainingType.ToString() == "TMPro.TMP_Text")
{
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context, symbol.Name, instanceExpression.ValueType, symbol.Parameters.Select(e => e.Type).ToArray(), symbol.ReturnType, symbol.IsStatic),
instanceExpression,
parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateBaseEnumMethodInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if ((symbol.Name == "ToString" || symbol.Name == "GetHashCode" || symbol.Name == "Equals") &&
symbol.ContainingType != null &&
symbol.ContainingType == context.GetTypeSymbol(SpecialType.System_Enum))
{
createdInvocation = new BoundExternInvocation(node, context,
context.GetTypeSymbol(SpecialType.System_Object).GetMember<MethodSymbol>(symbol.Name, context),
instanceExpression,
parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateCompareToInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if (symbol.Name == "CompareTo" &&
symbol.ContainingType != null &&
symbol.ContainingType == context.GetTypeSymbol(typeof(IComparable)))
{
createdInvocation = new BoundExternInvocation(node, context,
new ExternSynthesizedMethodSymbol(context, "CompareTo", instanceExpression.ValueType,
new [] { instanceExpression.ValueType },
context.GetTypeSymbol(SpecialType.System_Int32), false),
instanceExpression, parameterExpressions);
return true;
}
createdInvocation = null;
return false;
}
private static bool TryCreateShimInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions,
out BoundInvocationExpression createdInvocation)
{
if (TryCreateUdonSharpMetadataInvocation(context, node, symbol, instanceExpression, out createdInvocation))
return true;
if (TryCreateGetComponentInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateInstantiationInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateSetProgramVariableInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateArrayMethodInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateTMPMethodInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateBaseEnumMethodInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
if (TryCreateCompareToInvocation(context, node, symbol, instanceExpression, parameterExpressions, out createdInvocation))
return true;
return false;
}
public static BoundInvocationExpression CreateBoundInvocation(AbstractPhaseContext context, SyntaxNode node,
MethodSymbol symbol, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
{
if (TryCreateShimInvocation(context, node, symbol, instanceExpression, parameterExpressions, out var boundShimInvocation))
return boundShimInvocation;
if (symbol.IsExtern)
{
if (CompilerUdonInterface.IsUdonEvent(symbol) &&
symbol.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour))) // Pass through for making base calls on the U# behaviour type return noop
return new BoundUdonSharpBehaviourInvocationExpression(node, symbol, instanceExpression, parameterExpressions);
if (symbol.IsOperator)
{
// Enum equality/inequality
if (symbol.ContainingType?.IsEnum ?? false)
{
MethodSymbol objectEqualsMethod = context.GetTypeSymbol(SpecialType.System_Object)
.GetMember<MethodSymbol>("Equals", context);
var boundEqualsInvocation = CreateBoundInvocation(context, node, objectEqualsMethod, parameterExpressions[0],
new[] {parameterExpressions[1]});
if (symbol.Name == "op_Equality")
return boundEqualsInvocation;
MethodSymbol boolNotOperator = new ExternSynthesizedOperatorSymbol(
BuiltinOperatorType.UnaryNegation, context.GetTypeSymbol(SpecialType.System_Boolean),
context);
return new BoundExternInvocation(node, context, boolNotOperator, null, new BoundExpression[] {boundEqualsInvocation});
}
if (node is AssignmentExpressionSyntax)
return new BoundCompoundAssignmentExpression(context, node, (BoundAccessExpression) parameterExpressions[0], symbol, parameterExpressions[1]);
if (symbol is ExternBuiltinOperatorSymbol externBuiltinOperatorSymbol &&
externBuiltinOperatorSymbol.OperatorType == BuiltinOperatorType.BitwiseNot)
return new BoundBitwiseNotExpression(node, parameterExpressions[0]);
if (parameterExpressions.Length == 2 || symbol.Name == "op_UnaryNegation" || symbol.Name == "op_LogicalNot")
{
return new BoundBuiltinOperatorInvocationExpression(node, context, symbol, parameterExpressions);
}
throw new NotSupportedException("Operator expressions must have either 1 or 2 parameters", node.GetLocation());
}
return new BoundExternInvocation(node, context, symbol, instanceExpression, parameterExpressions);
}
if (symbol.IsStatic)
return new BoundStaticUserMethodInvocation(node, symbol, parameterExpressions);
if (symbol is UdonSharpBehaviourMethodSymbol udonSharpBehaviourMethodSymbol)
{
if (instanceExpression != null && !instanceExpression.IsThis)
udonSharpBehaviourMethodSymbol.MarkNeedsReferenceExport();
return new BoundUdonSharpBehaviourInvocationExpression(node, symbol, instanceExpression,
parameterExpressions);
}
throw new System.NotImplementedException();
}
protected override void ReleaseCowValuesImpl(EmitContext context)
{
if (ParameterExpressions == null)
return;
foreach (BoundExpression parameterExpression in ParameterExpressions)
{
parameterExpression.ReleaseCowReferences(context);
}
}
protected Value[] GetParameterValues(EmitContext context)
{
Value.CowValue[] parameterCows = context.GetExpressionCowValues(this, "parameters");
if (parameterCows != null)
return parameterCows.Select(e => e.Value).ToArray();
parameterCows = new Value.CowValue[ParameterExpressions.Length];
using (context.InterruptAssignmentScope())
{
for (int i = 0; i < parameterCows.Length; ++i)
parameterCows[i] = context.EmitValueWithDeferredRelease(ParameterExpressions[i])
.GetCowValue(context);
}
context.RegisterCowValues(parameterCows, this, "parameters");
Value[] parameterValues = new Value[ParameterExpressions.Length];
for (int i = 0; i < parameterValues.Length; ++i)
parameterValues[i] = parameterCows[i].Value;
return parameterValues;
}
protected Value.CowValue GetInstanceValue(EmitContext context)
{
Value.CowValue[] instanceValue = context.GetExpressionCowValues(this, "instance");
if (instanceValue == null)
{
using (context.InterruptAssignmentScope())
instanceValue = new[] {context.EmitValue(SourceExpression).GetCowValue(context)};
context.RegisterCowValues(instanceValue, this, "instance");
}
return instanceValue[0];
}
protected void CheckStackSize(Value valueCount, EmitContext context)
{
using (context.InterruptAssignmentScope())
{
Value stack = context.RecursiveStackValue;
BoundAccessExpression stackAccess = BoundAccessExpression.BindAccess(stack);
Value stackAddr = context.RecursiveStackAddressValue;
BoundAccessExpression stackAddrAccess = BoundAccessExpression.BindAccess(stackAddr);
TypeSymbol arrayType = context.GetTypeSymbol(SpecialType.System_Array);
context.Module.AddCommentTag("Stack size check");
// Check stack size and double it if it's not enough
// We know that doubling once will always be enough since the default size of the stack is the max number of stack values pushed in any method
PropertySymbol arraySizeProperty = arrayType.GetMember<PropertySymbol>("Length", context);
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
Value arraySize =
context.EmitValue(BoundAccessExpression.BindAccess(context, SyntaxNode, arraySizeProperty,
stackAccess));
BoundAccessExpression arraySizeAccess = BoundAccessExpression.BindAccess(arraySize);
Value targetSize = context.EmitValue(CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Addition, intType, context), null,
new BoundExpression[] { stackAddrAccess, BoundAccessExpression.BindAccess(valueCount) }));
Value isSizeGreaterThan = context.EmitValue(CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.GreaterThanOrEqual, intType, context), null,
new BoundExpression[]
{
BoundAccessExpression.BindAccess(targetSize),
arraySizeAccess,
}));
JumpLabel skipResizeLabel = context.Module.CreateLabel();
context.Module.AddJumpIfFalse(skipResizeLabel, isSizeGreaterThan);
// Resize logic
Value constantTwo = context.GetConstantValue(intType, 2);
Value newSize = context.EmitValue(CreateBoundInvocation(context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Multiplication, intType, context), null,
new BoundExpression[]
{
arraySizeAccess,
BoundAccessExpression.BindAccess(constantTwo),
}));
Value newArray = context.EmitValue(new BoundArrayCreationExpression(SyntaxNode, context,
context.GetTypeSymbol(SpecialType.System_Object).MakeArrayType(context),
new BoundExpression[] { BoundAccessExpression.BindAccess(newSize) }, null));
MethodSymbol arrayCopyMethod = arrayType.GetMembers<MethodSymbol>("Copy", context)
.First(e => e.Parameters.Length == 3 && e.Parameters[2].Type == intType);
context.Emit(CreateBoundInvocation(context, null, arrayCopyMethod, null,
new BoundExpression[]
{
stackAccess,
BoundAccessExpression.BindAccess(newArray),
BoundAccessExpression.BindAccess(arraySize)
}));
context.Module.AddCopy(newArray, stack);
context.Module.LabelJump(skipResizeLabel);
context.Module.AddCommentTag("Stack size check end");
}
}
protected void PushRecursiveValues(Value[] values, EmitContext context)
{
if (values.Length == 0)
return;
Value stack = context.RecursiveStackValue;
BoundAccessExpression stackAccess = BoundAccessExpression.BindAccess(stack);
Value stackAddr = context.RecursiveStackAddressValue;
BoundAccessExpression stackAddrAccess = BoundAccessExpression.BindAccess(stackAddr);
context.Module.AddCommentTag("Recursive stack push");
// Now we start copying values over to the stack
BoundInvocationExpression incrementExpression = new BoundPrefixOperatorExpression(context, SyntaxNode,
stackAddrAccess, new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Addition, context.GetTypeSymbol(SpecialType.System_Int32), context));
foreach (var valueToPush in values)
{
BoundArrayAccessExpression arraySet = new BoundArrayAccessExpression(null, context, stackAccess,
new BoundExpression[] { stackAddrAccess });
context.EmitSet(arraySet, BoundAccessExpression.BindAccess(valueToPush));
context.Emit(incrementExpression);
}
context.Module.AddCommentTag("Recursive stack push end");
}
protected void PopRecursiveValues(Value[] values, EmitContext context)
{
if (values.Length == 0)
return;
Value stack = context.RecursiveStackValue;
Value stackAddr = context.RecursiveStackAddressValue;
BoundAccessExpression stackAddrAccess = BoundAccessExpression.BindAccess(stackAddr);
TypeSymbol intType = context.GetTypeSymbol(SpecialType.System_Int32);
TypeSymbol objectType = context.GetTypeSymbol(SpecialType.System_Object);
TypeSymbol objectArrayType = objectType.MakeArrayType(context);
context.Module.AddCommentTag("Recursive stack pop");
BoundInvocationExpression decrementExpression = new BoundPrefixOperatorExpression(context, SyntaxNode,
stackAddrAccess, new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Subtraction, intType, context));
foreach (var valueToPop in values.Reverse())
{
context.Emit(decrementExpression);
ExternSynthesizedMethodSymbol arrayGetMethod = new ExternSynthesizedMethodSymbol(context, "Get",
objectArrayType, new [] { intType }, objectType, false);
context.Module.AddPush(stack);
context.Module.AddPush(stackAddr);
context.Module.AddPush(valueToPop);
context.Module.AddExtern(arrayGetMethod);
}
context.Module.AddCommentTag("Recursive stack pop end");
}
private sealed class BoundBuiltinOperatorInvocationExpression : BoundExternInvocation
{
public BoundBuiltinOperatorInvocationExpression(SyntaxNode node, AbstractPhaseContext context, MethodSymbol method, BoundExpression[] operandExpressions)
:base(node, context, method, null, operandExpressions)
{
}
}
private sealed class BoundCompoundAssignmentExpression : BoundInvocationExpression
{
private BoundAccessExpression TargetExpression { get; }
private BoundExpression AssignmentSource { get; }
private MethodSymbol OperatorMethod { get; }
public BoundCompoundAssignmentExpression(AbstractPhaseContext context, SyntaxNode node, BoundAccessExpression assignmentTarget, MethodSymbol operatorMethod, BoundExpression assignmentSource)
: base(node, null, null, null)
{
TargetExpression = assignmentTarget;
AssignmentSource = assignmentSource;
OperatorMethod = operatorMethod;
}
public override TypeSymbol ValueType => TargetExpression.ValueType;
public override Value EmitValue(EmitContext context)
{
Value targetValue =
context.EmitValueWithDeferredRelease(TargetExpression);
var invocation = CreateBoundInvocation(context, null, OperatorMethod, null,
new[] {BoundAccessExpression.BindAccess(targetValue), AssignmentSource});
Value setResult;
if (TargetExpression.ValueType != OperatorMethod.ReturnType)
setResult = context.EmitSet(TargetExpression, new BoundCastExpression(null, invocation, ValueType, true));
else
setResult = context.EmitSet(TargetExpression, invocation);
return setResult;
}
}
private sealed class BoundGetUnityEngineComponentInvocation : BoundExternInvocation
{
public override TypeSymbol ValueType { get; }
public BoundGetUnityEngineComponentInvocation(AbstractPhaseContext context, SyntaxNode node, MethodSymbol methodSymbol, BoundExpression sourceExpression, BoundExpression[] parametersExpressions)
: base(node, context, BuildMethod(context, methodSymbol), sourceExpression, GetParameterExpressions(context, methodSymbol, parametersExpressions))
{
ValueType = methodSymbol.TypeArguments[0];
if (methodSymbol.ReturnType.IsArray)
ValueType = ValueType.MakeArrayType(context);
}
private static BoundExpression[] GetParameterExpressions(AbstractPhaseContext context, MethodSymbol symbol, BoundExpression[] parameters)
{
BoundExpression typeExpression = new BoundConstantExpression(
symbol.TypeArguments[0].UdonType.SystemType,
context.GetTypeSymbol(typeof(Type)));
if (parameters == null || parameters.Length == 0)
return new [] { typeExpression };
return parameters.Concat(new []{typeExpression}).ToArray();
}
private static MethodSymbol BuildMethod(AbstractPhaseContext context, MethodSymbol methodSymbol)
{
string methodName = methodSymbol.Name;
string returnName;
if (methodSymbol.ReturnType.IsArray)
returnName = "__TArray";
else
returnName = "__T";
string paramStr = "";
if (methodSymbol.Parameters.Length > 0)
paramStr = "__SystemBoolean";
string methodIdentifier = $"UnityEngineComponent.__{methodName}{paramStr}{returnName}";
var roslynSymbol = methodSymbol.RoslynSymbol;
return new ExternSynthesizedMethodSymbol(context, methodIdentifier,
roslynSymbol.Parameters.Select(e => context.GetTypeSymbol(e.Type)).ToArray(),
context.GetTypeSymbol(roslynSymbol.ReturnType), false);
}
}
public sealed class BoundPostfixOperatorExpression : BoundInvocationExpression
{
private BoundAccessExpression TargetExpression { get; }
private BoundInvocationExpression InternalExpression { get; }
public BoundPostfixOperatorExpression(AbstractPhaseContext context, SyntaxNode node, BoundAccessExpression assignmentTarget, MethodSymbol operatorMethod)
: base(node, null, null, null)
{
TargetExpression = assignmentTarget;
Type targetType = TargetExpression.ValueType.UdonType.SystemType;
IConstantValue incrementValue = (IConstantValue) Activator.CreateInstance(
typeof(ConstantValue<>).MakeGenericType(targetType), Convert.ChangeType(1, targetType));
InternalExpression = CreateBoundInvocation(context, null, operatorMethod, null,
new BoundExpression[] {assignmentTarget, new BoundConstantExpression(incrementValue, TargetExpression.ValueType, node)});
}
public override TypeSymbol ValueType => TargetExpression.ValueType;
public override Value EmitValue(EmitContext context)
{
Value returnValue = context.GetReturnValue(TargetExpression.ValueType);
context.EmitValueAssignment(returnValue, TargetExpression);
Type targetType = TargetExpression.ValueType.UdonType.SystemType;
IConstantValue incrementValue = (IConstantValue) Activator.CreateInstance(
typeof(ConstantValue<>).MakeGenericType(targetType), Convert.ChangeType(1, targetType));
BoundExpression expression = CreateBoundInvocation(context, null, InternalExpression.Method, null,
new BoundExpression[] {BoundAccessExpression.BindAccess(returnValue), new BoundConstantExpression(incrementValue, TargetExpression.ValueType, SyntaxNode)});
if (InternalExpression.Method.ReturnType != TargetExpression.ValueType)
expression = new BoundCastExpression(null, expression, ValueType, true);
context.EmitSet(TargetExpression, expression);
return returnValue;
}
/// <summary>
/// If we aren't requesting a value, we can just direct assign.
/// This helps keep increments on stuff like loops with i++ fast
/// </summary>
/// <param name="context"></param>
public override void Emit(EmitContext context)
{
if (InternalExpression.Method.ReturnType != TargetExpression.ValueType)
context.EmitSet(TargetExpression, new BoundCastExpression(null, InternalExpression, ValueType, true));
else
context.EmitSet(TargetExpression, InternalExpression);
}
}
public sealed class BoundPrefixOperatorExpression : BoundInvocationExpression
{
private BoundAccessExpression TargetExpression { get; }
private BoundInvocationExpression InternalExpression { get; }
public BoundPrefixOperatorExpression(AbstractPhaseContext context, SyntaxNode node, BoundAccessExpression assignmentTarget, MethodSymbol operatorMethod)
: base(node, null, null, null)
{
TargetExpression = assignmentTarget;
Type targetType = TargetExpression.ValueType.UdonType.SystemType;
IConstantValue incrementValue = (IConstantValue) Activator.CreateInstance(
typeof(ConstantValue<>).MakeGenericType(targetType), Convert.ChangeType(1, targetType));
InternalExpression = CreateBoundInvocation(context, null, operatorMethod, null,
new BoundExpression[] {assignmentTarget, new BoundConstantExpression(incrementValue, TargetExpression.ValueType, node)});
}
public override TypeSymbol ValueType => TargetExpression.ValueType;
public override Value EmitValue(EmitContext context)
{
if (InternalExpression.Method.ReturnType != TargetExpression.ValueType)
return context.EmitSet(TargetExpression, new BoundCastExpression(null, InternalExpression, ValueType, true));
return context.EmitSet(TargetExpression, InternalExpression);
}
}
public sealed class BoundConstantInvocationExpression : BoundInvocationExpression
{
private IConstantValue Constant { get; }
public override IConstantValue ConstantValue => Constant;
public override TypeSymbol ValueType { get; }
public BoundConstantInvocationExpression(SyntaxNode node, IConstantValue constantValue, TypeSymbol constantValType)
:base(node, null, null, Array.Empty<BoundExpression>())
{
Constant = constantValue;
ValueType = constantValType;
}
public override Value EmitValue(EmitContext context)
{
Value returnVal = context.GetReturnValue(ValueType);
context.EmitValueAssignment(returnVal,
BoundAccessExpression.BindAccess(context.GetConstantValue(ValueType, ConstantValue.Value)));
return returnVal;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 45b01b776c06154438ab739241a968fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@

using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundLocalDeclarationStatement : BoundStatement
{
private BoundVariableDeclarationStatement DeclarationStatement { get; }
public BoundLocalDeclarationStatement(LocalDeclarationStatementSyntax node, BoundVariableDeclarationStatement declarationStatement)
: base(node)
{
DeclarationStatement = declarationStatement;
}
public override void Emit(EmitContext context)
{
context.Emit(DeclarationStatement);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 99ab1957216e3a14a907ce6428284c0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,24 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
/// <summary>
/// Base for bound expressions and statements, largely mirroring the Roslyn internal layout for the compiler
/// </summary>
internal abstract class BoundNode
{
public SyntaxNode SyntaxNode { get; }
protected BoundNode(SyntaxNode node)
{
SyntaxNode = node;
}
public virtual void Emit(EmitContext context)
{
throw new System.NotImplementedException($"Emit is not implemented on {GetType()}");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81ccd6272f612a642bddbe21f975e0fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,263 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UnityEngine;
using VRC.Udon.Common.Interfaces;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundPropertyAccessExpression : BoundAccessExpression
{
protected PropertySymbol Property { get; }
private BoundExpression[] ParameterExpressions { get; }
public override TypeSymbol ValueType => Property.Type;
private bool _isBaseCall;
public override void MarkForcedBaseCall() => _isBaseCall = true;
protected BoundPropertyAccessExpression(AbstractPhaseContext context, SyntaxNode node, PropertySymbol property, BoundExpression sourceExpression, BoundExpression[] parameterExpressions)
: base(node, sourceExpression)
{
Property = property;
ParameterExpressions = parameterExpressions;
}
private BoundExpression[] GetParameters(EmitContext context, BoundExpression valueExpression = null)
{
using (context.InterruptAssignmentScope())
{
Value.CowValue[] propertyParams = context.GetExpressionCowValues(this, "propertyParams");
if (propertyParams == null)
{
if (ParameterExpressions != null)
{
propertyParams = new Value.CowValue[ParameterExpressions.Length];
for (int i = 0; i < propertyParams.Length; ++i)
propertyParams[i] = context.EmitValue(ParameterExpressions[i]).GetCowValue(context);
}
else
{
propertyParams = Array.Empty<Value.CowValue>();
}
context.RegisterCowValues(propertyParams, this, "propertyParams");
}
List<BoundExpression> expressions = new List<BoundExpression>();
expressions.AddRange(propertyParams.Select(BindAccess));
if (valueExpression != null)
expressions.Add(valueExpression);
return expressions.ToArray();
}
}
private BoundExpression GetInstanceExpression(EmitContext context)
{
if (SourceExpression == null)
return null;
if (SourceExpression.IsThis)
return SourceExpression;
Value.CowValue[] instance = context.GetExpressionCowValues(this, "propertyInstance");
if (instance == null)
{
using (context.InterruptAssignmentScope())
instance = new[] {context.EmitValue(SourceExpression).GetCowValue(context)};
context.RegisterCowValues(instance, this, "propertyInstance");
}
return BindAccess(instance[0]);
}
public override Value EmitValue(EmitContext context)
{
BoundInvocationExpression invocationExpression = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
Property.GetMethod,
GetInstanceExpression(context), GetParameters(context));
if (_isBaseCall)
invocationExpression.MarkForcedBaseCall();
return context.EmitValue(invocationExpression);
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
BoundExpression instanceValue = GetInstanceExpression(context);
BoundInvocationExpression invocationExpression = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode,
Property.SetMethod,
instanceValue, GetParameters(context, valueExpression));
if (_isBaseCall)
invocationExpression.MarkForcedBaseCall();
invocationExpression.MarkPropertySetter();
Value resultVal = context.EmitValue(invocationExpression);
if (resultVal == null)
throw new NullReferenceException();
if (instanceValue != null &&
SourceExpression.ValueType.IsValueType &&
SourceExpression is BoundArrayAccessExpression sourceAccessExpression)
{
context.EmitSet(sourceAccessExpression, instanceValue);
}
return resultVal;
}
private static readonly HashSet<Type> _allowedConstantPropertyTypes = new HashSet<Type>()
{
typeof(Vector2),
typeof(Vector3),
typeof(Vector4),
typeof(Quaternion),
typeof(Matrix4x4),
typeof(Color),
typeof(Mathf),
typeof(Rect),
};
public static BoundAccessExpression BindPropertyAccess(AbstractPhaseContext context, SyntaxNode node, PropertySymbol propertySymbol, BoundExpression sourceExpression, BoundExpression[] parameterExpressions = null)
{
if (propertySymbol is ExternPropertySymbol externProperty)
{
Type propertyType = externProperty.ContainingType.UdonType.SystemType;
if (_allowedConstantPropertyTypes.Contains(propertyType))
{
PropertyInfo property = propertyType.GetProperty(externProperty.Name,
BindingFlags.Static | BindingFlags.Public);
if (property != null)
return new BoundConstantExpression(property.GetValue(null), propertySymbol.Type);
}
if (propertySymbol.ToString() == "UnityEngine.Behaviour.enabled")
return new BoundEnabledPropertyExternAccessExpression(context, node, sourceExpression);
if (propertySymbol.ContainingType.ToString() == "TMPro.TMP_Text")
return new BoundTMPPropertyExternAccessExpression(context, node, externProperty, sourceExpression);
return new BoundExternPropertyAccessExpression(context, node, externProperty, sourceExpression, parameterExpressions);
}
return new BoundUserPropertyAccessExpression(context, node, propertySymbol, sourceExpression, parameterExpressions);
}
private sealed class BoundUserPropertyAccessExpression : BoundPropertyAccessExpression
{
public BoundUserPropertyAccessExpression(AbstractPhaseContext context, SyntaxNode node, PropertySymbol property, BoundExpression sourceExpression, BoundExpression[] parameterExpressions)
:base(context, node, property, sourceExpression, parameterExpressions)
{
}
}
private sealed class BoundExternPropertyAccessExpression : BoundPropertyAccessExpression
{
public BoundExternPropertyAccessExpression(AbstractPhaseContext context, SyntaxNode node, ExternPropertySymbol property, BoundExpression sourceExpression, BoundExpression[] parameterExpressions)
: base(context, node, property, sourceExpression, parameterExpressions)
{
}
/// <summary>
/// Optimize self refs to gameObject and transform since Udon supplies them
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private Value TryHandleUnityEngineSelfReference(EmitContext context)
{
string propertyStr = Property.ToString();
if (propertyStr != "UnityEngine.Component.transform" &&
propertyStr != "UnityEngine.Component.gameObject")
return null;
if (SourceExpression != null && !SourceExpression.IsThis)
return null;
return context.GetUdonThisValue(Property.Type);
}
public override Value EmitValue(EmitContext context)
{
Value returnValue = TryHandleUnityEngineSelfReference(context);
if (returnValue != null)
return returnValue;
return base.EmitValue(context);
}
}
/// <summary>
/// Handles how Udon does not expose the base Behaviour.enabled, but exposes the derived versions
/// </summary>
private sealed class BoundEnabledPropertyExternAccessExpression : BoundPropertyAccessExpression
{
public override TypeSymbol ValueType { get; }
public BoundEnabledPropertyExternAccessExpression(AbstractPhaseContext context, SyntaxNode node, BoundExpression sourceExpression)
: base(context, node, BuildProperty(context, sourceExpression), sourceExpression, null)
{
ValueType = context.GetTypeSymbol(SpecialType.System_Boolean);
}
private static PropertySymbol BuildProperty(AbstractPhaseContext context, BoundExpression sourceExpression)
{
TypeSymbol propertyType = sourceExpression.ValueType;
if (propertyType.UdonType.ExternSignature == "VRCUdonUdonBehaviour")
propertyType = context.GetTypeSymbol(typeof(IUdonEventReceiver));
TypeSymbol boolType = context.GetTypeSymbol(SpecialType.System_Boolean);
MethodSymbol setMethod = new ExternSynthesizedMethodSymbol(context, "set_enabled", propertyType,
new[] {boolType}, null, false);
MethodSymbol getMethod = new ExternSynthesizedMethodSymbol(context, "get_enabled", propertyType,
new TypeSymbol[] {}, boolType, false);
return new SynthesizedPropertySymbol(context, getMethod, setMethod);
}
}
private sealed class BoundTMPPropertyExternAccessExpression : BoundPropertyAccessExpression
{
public override TypeSymbol ValueType { get; }
public BoundTMPPropertyExternAccessExpression(AbstractPhaseContext context, SyntaxNode node, PropertySymbol propertySymbol, BoundExpression sourceExpression)
: base(context, node, BuildProperty(context, sourceExpression, propertySymbol), sourceExpression, null)
{
ValueType = propertySymbol.Type;
}
private static PropertySymbol BuildProperty(AbstractPhaseContext context, BoundExpression sourceExpression, PropertySymbol propertySymbol)
{
TypeSymbol propertyType = sourceExpression.ValueType;
if (propertyType.UdonType.ExternSignature == "VRCUdonUdonBehaviour")
propertyType = context.GetTypeSymbol(typeof(IUdonEventReceiver));
MethodSymbol setMethod = new ExternSynthesizedMethodSymbol(context, $"set_{propertySymbol.Name}", propertyType,
new[] {propertySymbol.Type}, null, false);
MethodSymbol getMethod = new ExternSynthesizedMethodSymbol(context, $"get_{propertySymbol.Name}", propertyType,
new TypeSymbol[] {}, propertySymbol.Type, false);
return new SynthesizedPropertySymbol(context, getMethod, setMethod);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ac35632a6bec5541ba54c611c7be6e4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundReturnStatement : BoundStatement
{
private BoundExpression ReturnExpression { get; }
public BoundReturnStatement(SyntaxNode node, BoundExpression returnExpression)
:base(node)
{
ReturnExpression = returnExpression;
}
public override void Emit(EmitContext context)
{
context.EmitReturn(ReturnExpression);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47b84e2be9d6eca4ead00c530f50485d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,72 @@

using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundShortCircuitOperatorExpression : BoundExpression
{
private BoundExpression Lhs { get; }
private BoundExpression Rhs { get; }
private BuiltinOperatorType OperatorType { get; }
public override TypeSymbol ValueType { get; }
public BoundShortCircuitOperatorExpression(BinaryExpressionSyntax node, BuiltinOperatorType operatorType, BoundExpression lhs, BoundExpression rhs, AbstractPhaseContext context)
: base(node)
{
ValueType = context.GetTypeSymbol(SpecialType.System_Boolean);
Lhs = lhs;
Rhs = rhs;
OperatorType = operatorType;
}
public override Value EmitValue(EmitContext context)
{
// We don't want any references outside the flow control to be dirtied conditionally
context.TopTable.DirtyAllValues();
Value resultValue = context.CreateInternalValue(ValueType);
if (OperatorType == BuiltinOperatorType.LogicalAnd)
{
JumpLabel failLabel = context.Module.CreateLabel();
context.EmitValueAssignment(resultValue, Lhs);
context.Module.AddJumpIfFalse(failLabel, resultValue);
context.EmitValueAssignment(resultValue, Rhs);
context.Module.LabelJump(failLabel);
}
else if (OperatorType == BuiltinOperatorType.LogicalOr)
{
JumpLabel failLabel = context.Module.CreateLabel();
JumpLabel exitLabel = context.Module.CreateLabel();
context.EmitValueAssignment(resultValue, Lhs);
context.Module.AddJumpIfFalse(failLabel, resultValue);
context.Module.AddJump(exitLabel);
context.Module.LabelJump(failLabel);
context.EmitValueAssignment(resultValue, Rhs);
context.Module.LabelJump(exitLabel);
}
else
{
throw new InvalidOperationException("Invalid operator type");
}
return resultValue;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ca2c2ebc93520e4fb348cac7cccaa5b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@

using Microsoft.CodeAnalysis;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundStatement : BoundNode
{
protected BoundStatement(SyntaxNode node)
:base(node)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a01e89746ce9024ca0ea57d77f9113c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundStaticUserMethodInvocation : BoundUserMethodInvocationExpression
{
public BoundStaticUserMethodInvocation(SyntaxNode node, MethodSymbol method, BoundExpression[] parameterExpressions)
:base(node, method, null, parameterExpressions)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9e900cfa7f9ba7448af747103b025bc4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,52 @@

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundStringAccessExpression : BoundAccessExpression
{
private BoundExpression IndexerExpression { get; }
private readonly MethodSymbol _toCharArraySymbol;
public BoundStringAccessExpression(AbstractPhaseContext context, SyntaxNode node, BoundExpression sourceExpression, BoundExpression indexerExpression)
: base(node, sourceExpression)
{
IndexerExpression = indexerExpression;
ValueType = context.GetTypeSymbol(SpecialType.System_Char);
_toCharArraySymbol = context.GetTypeSymbol(SpecialType.System_String).GetMembers<MethodSymbol>(nameof(string.ToCharArray), context).First(e => e.Parameters.Length == 2);
}
public override TypeSymbol ValueType { get; }
public override Value EmitValue(EmitContext context)
{
Value returnValue = context.GetReturnValue(ValueType);
var charArray = BoundInvocationExpression.CreateBoundInvocation(context, SyntaxNode, _toCharArraySymbol,
BindAccess(context.EmitValue(SourceExpression)), new[]
{
IndexerExpression,
BindAccess(context.GetConstantValue(context.GetTypeSymbol(SpecialType.System_Int32), 1))
});
context.EmitValueAssignment(returnValue,
BindElementAccess(context, SyntaxNode, charArray,
new BoundExpression[]
{
BindAccess(context.GetConstantValue(context.GetTypeSymbol(SpecialType.System_Int32), 0))
}));
return returnValue;
}
public override Value EmitSet(EmitContext context, BoundExpression valueExpression)
{
throw new InvalidOperationException("Cannot set a character on a string");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bd86ebdb185698468f73ebb4d4a57ca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundSwitchStatement : BoundStatement
{
private BoundExpression SwitchExpression { get; }
private List<(List<BoundExpression>, List<BoundStatement>)> SwitchSections { get; }
private int DefaultSectionIdx { get; }
public BoundSwitchStatement(SyntaxNode node, BoundExpression expression, List<(List<BoundExpression>, List<BoundStatement>)> sections, int defaultSectionIdx)
:base(node)
{
SwitchExpression = expression;
SwitchSections = sections;
DefaultSectionIdx = defaultSectionIdx;
}
public override void Emit(EmitContext context)
{
// Todo: look at adding binary search and then dictionary lookups as fallbacks for especially large switches that can't use jump tables
if (IsJumpTableCandidate())
EmitJumpTableSwitchStatement(context);
else
EmitDefaultSwitchStatement(context);
}
private void EmitDefaultSwitchStatement(EmitContext context)
{
JumpLabel breakLabel = context.PushBreakLabel();
JumpLabel defaultLabel = context.Module.CreateLabel();
Value switchConditionVal = context.EmitValue(SwitchExpression);
var conditionAccess = BoundAccessExpression.BindAccess(switchConditionVal);
TypeSymbol objectType = context.GetTypeSymbol(SpecialType.System_Object);
MethodSymbol objectEqualityMethod = null;
// If switch is over object we need to check if it's null first and jump to the default if it is
if (SwitchExpression.ValueType == objectType)
{
objectEqualityMethod = objectType.GetMember<MethodSymbol>("Equals", context);
Value conditionCheck = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(
context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Inequality,
objectType, context), null,
new BoundExpression[] { conditionAccess, BoundAccessExpression.BindAccess(context.GetConstantValue(objectType, null)) }));
context.Module.AddJumpIfFalse(defaultLabel, conditionCheck);
}
JumpLabel nextLabel = context.Module.CreateLabel();
using (context.OpenBlockScope())
{
for (int i = 0; i < SwitchSections.Count; ++i)
{
var section = SwitchSections[i];
JumpLabel sectionBodyLabel = context.Module.CreateLabel();
foreach (BoundExpression labelExpression in section.Item1)
{
context.Module.LabelJump(nextLabel);
nextLabel = context.Module.CreateLabel();
Value conditionCheck;
if (SwitchExpression.ValueType == objectType)
{
conditionCheck = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(
context, SyntaxNode, objectEqualityMethod, conditionAccess,
new[] { labelExpression }));
}
else
{
conditionCheck = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(
context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.Equality,
switchConditionVal.UdonType, context), null,
new[] { conditionAccess, labelExpression }));
}
context.Module.AddJumpIfFalse(nextLabel, conditionCheck);
if (section.Item1.Count > 1)
context.Module.AddJump(sectionBodyLabel);
}
if (i == DefaultSectionIdx)
{
context.Module.AddJump(nextLabel);
context.Module.LabelJump(defaultLabel);
}
context.Module.LabelJump(sectionBodyLabel);
foreach (BoundStatement statement in section.Item2)
{
context.Emit(statement);
}
}
context.Module.LabelJump(nextLabel);
if (DefaultSectionIdx != -1)
context.Module.AddJump(defaultLabel);
else
context.Module.LabelJump(defaultLabel);
context.Module.LabelJump(breakLabel);
}
context.PopBreakLabel();
}
private void EmitJumpTableSwitchStatement(EmitContext context)
{
Value expressionValue = context.EmitValue(SwitchExpression);
JumpLabel exitLabel = context.PushBreakLabel();
JumpLabel defaultJump = context.Module.CreateLabel();
int maxValue = 0;
foreach (var switchSection in SwitchSections)
{
foreach (var expression in switchSection.Item1)
maxValue = Math.Max(maxValue, Convert.ToInt32(expression.ConstantValue.Value));
}
Value greaterThanZeroCondition = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(
context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.GreaterThanOrEqual,
expressionValue.UdonType, context), null,
new[]
{
BoundAccessExpression.BindAccess(expressionValue),
BoundAccessExpression.BindAccess(context.GetConstantValue(expressionValue.UdonType,
Convert.ChangeType(0, expressionValue.UdonType.SystemType)))
}));
context.Module.AddJumpIfFalse(defaultJump, greaterThanZeroCondition);
Value lessThanMaxCondition = context.EmitValue(BoundInvocationExpression.CreateBoundInvocation(
context, SyntaxNode,
new ExternSynthesizedOperatorSymbol(BuiltinOperatorType.LessThanOrEqual,
expressionValue.UdonType, context), null,
new[]
{
BoundAccessExpression.BindAccess(expressionValue),
BoundAccessExpression.BindAccess(context.GetConstantValue(expressionValue.UdonType,
Convert.ChangeType(maxValue, expressionValue.UdonType.SystemType)))
}));
context.Module.AddJumpIfFalse(defaultJump, lessThanMaxCondition);
Value convertedValue = context.CastValue(expressionValue, context.GetTypeSymbol(SpecialType.System_Int32), true);
Value jumpTable = context.CreateGlobalInternalValue(context.GetTypeSymbol(SpecialType.System_UInt32).MakeArrayType(context));
Value jumpAddress = context.EmitValue(BoundAccessExpression.BindElementAccess(context, SyntaxNode,
BoundAccessExpression.BindAccess(jumpTable),
new BoundExpression[] { BoundAccessExpression.BindAccess(convertedValue) }));
context.Module.AddJumpIndrect(jumpAddress);
uint[] jumpTableArr = new uint[maxValue + 1];
using (context.OpenBlockScope())
{
for (int i = 0; i < SwitchSections.Count; ++i)
{
var switchSection = SwitchSections[i];
JumpLabel currentPos = context.Module.CreateLabel();
context.Module.LabelJump(currentPos);
if (DefaultSectionIdx == i)
context.Module.LabelJump(defaultJump);
foreach (BoundExpression labelExpression in switchSection.Item1)
{
int labelIdx = Convert.ToInt32(labelExpression.ConstantValue.Value);
jumpTableArr[labelIdx] = currentPos.Address;
}
foreach (BoundStatement statement in switchSection.Item2)
{
context.Emit(statement);
}
}
}
if (DefaultSectionIdx == -1)
context.Module.LabelJump(defaultJump);
context.Module.LabelJump(exitLabel);
context.PopBreakLabel();
for (int i = 0; i < jumpTableArr.Length; ++i)
{
if (jumpTableArr[i] == 0)
jumpTableArr[i] = defaultJump.Address;
}
jumpTable.DefaultValue = jumpTableArr;
}
private const int JUMP_TABLE_MAX = 256;
private bool IsJumpTableCandidate()
{
if (!UdonSharpUtils.IsIntegerType(SwitchExpression.ValueType.UdonType.SystemType))
return false;
int labelCount = 0;
foreach (var switchSection in SwitchSections)
{
foreach (var expression in switchSection.Item1)
{
labelCount++;
if (expression.ConstantValue.Value is ulong ulongVal && (ulongVal > JUMP_TABLE_MAX))
return false;
long intVal = Convert.ToInt64(expression.ConstantValue.Value);
if (intVal > JUMP_TABLE_MAX || intVal < 0)
return false;
}
}
if (labelCount < 4)
return false;
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 093f1060b48d746498a98d436f413b29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,183 @@

using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Core;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundUdonSharpBehaviourInvocationExpression : BoundUserMethodInvocationExpression
{
public BoundUdonSharpBehaviourInvocationExpression(SyntaxNode node, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
:base(node, method, instanceExpression, parameterExpressions)
{
}
public override Value EmitValue(EmitContext context)
{
// Make base calls to UdonSharpBehaviour events a noop
if (IsBaseCall)
{
if (Method.ContainingType == context.GetTypeSymbol(typeof(UdonSharpBehaviour)))
{
if (Method.Name == "OnOwnershipRequest")
return context.GetConstantValue(context.GetTypeSymbol(SpecialType.System_Boolean), true);
return null;
}
}
if (SourceExpression == null || SourceExpression.IsThis)
return base.EmitValue(context);
// Calls across UdonBehaviours
CompilationContext.MethodExportLayout layout =
context.CompileContext.GetUsbMethodLayout(Method, context);
Value.CowValue instanceCowValue = GetInstanceValue(context);
Value instanceValue = instanceCowValue.Value;
BoundAccessExpression instanceAccess = BoundAccessExpression.BindAccess(instanceValue);
TypeSymbol stringType = context.GetTypeSymbol(SpecialType.System_String);
Value[] recursiveValues = null;
Value stackSizeCheckVal = null;
bool isRecursiveCall = context.IsRecursiveMethodEmit;
if (isRecursiveCall)
{
stackSizeCheckVal = context.CreateGlobalInternalValue(context.GetTypeSymbol(SpecialType.System_Int32));
}
if (Method.Parameters.Length > 0)
{
MethodSymbol setProgramVariableMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour))
.GetMembers<MethodSymbol>("SetProgramVariable", context)
.First(e => e.Parameters.Length == 2 &&
e.Parameters[0].Type == stringType);
Value[] parameterValues = GetParameterValues(context);
instanceValue = instanceCowValue.Value;
instanceAccess = BoundAccessExpression.BindAccess(instanceValue); // Re-bind here since the parameters may have changed the cowvalue
if (isRecursiveCall)
{
EmitContext.MethodLinkage selfLinkage = context.GetMethodLinkage(context.CurrentEmitMethod, false);
recursiveValues = selfLinkage.ParameterValues;
CheckStackSize(stackSizeCheckVal, context);
PushRecursiveValues(recursiveValues, context);
for (int i = 0; i < parameterValues.Length; ++i)
{
Value paramIntermediate = context.CreateInternalValue(parameterValues[i].UserType);
context.Module.AddCopy(parameterValues[i], paramIntermediate);
parameterValues[i] = paramIntermediate;
}
}
else
{
instanceCowValue.Dispose();
}
context.TopTable.DirtyAllValues();
for (int i = 0; i < Method.Parameters.Length; ++i)
{
context.Emit(CreateBoundInvocation(context, SyntaxNode, setProgramVariableMethod, instanceAccess,
new BoundExpression[]
{
BoundAccessExpression.BindAccess(context.GetConstantValue(stringType,layout.ParameterExportNames[i])),
BoundAccessExpression.BindAccess(parameterValues[i])
}));
}
}
if (isRecursiveCall)
{
if (recursiveValues == null)
recursiveValues = Array.Empty<Value>();
Value[] scopeValues = context.CollectRecursiveValues();
PushRecursiveValues(scopeValues, context);
recursiveValues = recursiveValues.Concat(scopeValues).ToArray();
stackSizeCheckVal.DefaultValue = recursiveValues.Length;
context.UpdateRecursiveStackMaxSize(recursiveValues.Length);
}
MethodSymbol sendCustomEventMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour)).GetMember<MethodSymbol>("SendCustomEvent", context);
context.Emit(CreateBoundInvocation(context, SyntaxNode, sendCustomEventMethod, BoundAccessExpression.BindAccess(instanceValue),
new BoundExpression[]
{
BoundAccessExpression.BindAccess(context.GetConstantValue(stringType, layout.ExportMethodName))
}));
if (isRecursiveCall)
PopRecursiveValues(recursiveValues, context);
if (Method.Parameters.Length > 0 &&
Method.Parameters.Any(e => e.IsOut))
{
if (isRecursiveCall)
throw new CompilerException("U# does not yet support calling user methods with ref/out parameters from methods marked with RecursiveMethod");
MethodSymbol getProgramVariableMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour))
.GetMembers<MethodSymbol>("GetProgramVariable", context)
.First(e => e.Parameters.Length == 1 &&
e.Parameters[0].Type == stringType);
// Copy out/ref parameters back
for (int i = 0; i < Method.Parameters.Length; ++i)
{
ParameterSymbol parameterSymbol = Method.Parameters[i];
if (parameterSymbol.IsOut)
{
BoundAccessExpression currentAccessExpression = (BoundAccessExpression)ParameterExpressions[i];
currentAccessExpression.EmitSet(context, CreateBoundInvocation(context, SyntaxNode,
getProgramVariableMethod, instanceAccess, new[]
{
BoundAccessExpression.BindAccess(context.GetConstantValue(stringType,
layout.ParameterExportNames[i]))
}));
}
}
}
if (IsPropertySetter)
{
return GetParameterValues(context).Last();
}
if (Method.ReturnType != null)
{
MethodSymbol getProgramVariableMethod = context.GetTypeSymbol(typeof(UdonSharpBehaviour))
.GetMembers<MethodSymbol>("GetProgramVariable", context)
.First(e => e.Parameters.Length == 1 &&
e.Parameters[0].Type == stringType);
Value returnVal = context.CreateInternalValue(Method.ReturnType);
BoundInvocationExpression boundGetReturn = CreateBoundInvocation(context, SyntaxNode,
getProgramVariableMethod, instanceAccess,
new BoundExpression[]
{
BoundAccessExpression.BindAccess(context.GetConstantValue(stringType,
layout.ReturnExportName))
});
context.EmitValueAssignment(returnVal, boundGetReturn);
return returnVal;
}
return null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b31b1704bbe04d6478cc201357e1d414
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,133 @@

using System.Linq;
using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
using UdonSharp.Core;
namespace UdonSharp.Compiler.Binder
{
internal abstract class BoundUserMethodInvocationExpression : BoundInvocationExpression
{
protected BoundUserMethodInvocationExpression(SyntaxNode node, MethodSymbol method, BoundExpression instanceExpression, BoundExpression[] parameterExpressions)
: base(node, method, instanceExpression, parameterExpressions)
{
}
protected bool IsBaseCall { get; private set; }
public override void MarkForcedBaseCall()
{
IsBaseCall = true;
}
public override Value EmitValue(EmitContext context)
{
JumpLabel returnPoint = context.Module.CreateLabel();
Value returnPointVal =
context.CreateGlobalInternalValue(context.GetTypeSymbol(SpecialType.System_UInt32));
context.Module.AddPush(returnPointVal);
var linkage = context.GetMethodLinkage(Method, !IsBaseCall);
Value[] parameterValues = GetParameterValues(context);
Value[] recursiveValues = null;
bool isRecursiveCall = context.IsRecursiveMethodEmit;
Value stackSizeCheckVal = null;
if (isRecursiveCall)
{
EmitContext.MethodLinkage selfLinkage = context.GetMethodLinkage(context.CurrentEmitMethod, false);
recursiveValues = selfLinkage.ParameterValues;
stackSizeCheckVal = context.CreateGlobalInternalValue(context.GetTypeSymbol(SpecialType.System_Int32));
CheckStackSize(stackSizeCheckVal, context);
PushRecursiveValues(selfLinkage.ParameterValues, context);
}
ReleaseCowReferences(context);
if (isRecursiveCall)
{
Value.CowValue[] paramCows = parameterValues.Select(e => e.GetCowValue(context)).ToArray();
for (int i = 0; i < linkage.ParameterValues.Length; ++i)
context.EmitValueAssignment(linkage.ParameterValues[i], BoundAccessExpression.BindAccess(paramCows[i]));
foreach (var paramCow in paramCows)
paramCow.Dispose();
}
else
{
for (int i = 0; i < linkage.ParameterValues.Length; ++i)
context.Module.AddCopy(parameterValues[i], linkage.ParameterValues[i]);
}
context.TopTable.DirtyAllValues();
if (isRecursiveCall)
{
Value[] collectedValues = context.CollectRecursiveValues().Where(e => !recursiveValues.Contains(e)).ToArray();
PushRecursiveValues(collectedValues, context);
recursiveValues = recursiveValues.Concat(collectedValues).ToArray();
stackSizeCheckVal.DefaultValue = recursiveValues.Length;
context.UpdateRecursiveStackMaxSize(recursiveValues.Length);
}
context.Module.AddCommentTag($"Calling {Method}");
context.Module.AddJump(linkage.MethodLabel);
context.Module.LabelJump(returnPoint);
returnPointVal.DefaultValue = returnPoint.Address;
Value recursiveRet = null;
if (isRecursiveCall)
{
if (linkage.ReturnValue != null)
{
recursiveRet = context.CreateInternalValue(linkage.ReturnValue.UserType);
context.Module.AddCopy(linkage.ReturnValue, recursiveRet);
}
PopRecursiveValues(recursiveValues, context);
}
// Handle out/ref parameters
for (int i = 0; i < Method.Parameters.Length; ++i)
{
if (!Method.Parameters[i].IsOut) continue;
if (isRecursiveCall)
throw new CompilerException("U# does not yet support calling user methods with ref/out parameters from methods marked with RecursiveMethod");
BoundAccessExpression paramAccess = (BoundAccessExpression)ParameterExpressions[i];
paramAccess.EmitSet(context, BoundAccessExpression.BindAccess(linkage.ParameterValues[i]));
}
// Properties need to return the value that they are set to for assignment expressions
if (IsPropertySetter)
{
return parameterValues.Last();
}
if (Method.ReturnType != null)
{
if (isRecursiveCall)
return recursiveRet;
return linkage.ReturnValue;
}
return null;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8abfdc03d4bdc4943883d8165c9947d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@

using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundVariableDeclarationStatement : BoundStatement
{
private BoundVariableDeclaratorStatement[] Declarations { get; }
public BoundVariableDeclarationStatement(VariableDeclarationSyntax node, BoundVariableDeclaratorStatement[] declarations)
: base(node)
{
Declarations = declarations;
}
public override void Emit(EmitContext context)
{
foreach (BoundVariableDeclaratorStatement declaration in Declarations)
context.Emit(declaration);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4f22848a9d58a4e4387fc73b6dc122e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,32 @@

using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Emit;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundVariableDeclaratorStatement : BoundStatement
{
public Symbol UserSymbol { get; }
public BoundExpression Initializer { get; }
public BoundVariableDeclaratorStatement(VariableDeclaratorSyntax node, Symbol userSymbol, BoundExpression initializer)
: base(node)
{
UserSymbol = userSymbol;
Initializer = initializer;
}
public override void Emit(EmitContext context)
{
if (UserSymbol is LocalSymbol localSymbol && localSymbol.IsConst)
return;
Value userValue = context.GetUserValue(UserSymbol);
if (Initializer == null) return;
context.EmitValueAssignment(userValue, Initializer);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0145f9ed82b8d148ab8d61814523e8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,39 @@

using Microsoft.CodeAnalysis;
using UdonSharp.Compiler.Assembly;
using UdonSharp.Compiler.Emit;
namespace UdonSharp.Compiler.Binder
{
internal sealed class BoundWhileStatement : BoundStatement
{
private BoundExpression Condition { get; }
private BoundStatement Body { get; }
public BoundWhileStatement(SyntaxNode node, BoundExpression condition, BoundStatement body)
:base(node)
{
Condition = condition;
Body = body;
}
public override void Emit(EmitContext context)
{
JumpLabel continueLabel = context.PushContinueLabel();
JumpLabel breakLabel = context.PushBreakLabel();
context.Module.LabelJump(continueLabel);
context.Module.AddJumpIfFalse(breakLabel, context.EmitValue(Condition));
context.Emit(Body);
context.Module.AddJump(continueLabel);
context.Module.LabelJump(breakLabel);
context.PopBreakLabel();
context.PopContinueLabel();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8b1da9db75d2ce44b62a3ac12e77199
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,389 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using UdonSharp.Compiler.Symbols;
namespace UdonSharp.Compiler.Binder
{
internal static class ConstantExpressionOptimizer
{
private static class DynamicInvoke
{
public static dynamic Add(dynamic a, dynamic b) => a + b;
public static dynamic Sub(dynamic a, dynamic b) => a - b;
public static dynamic Mul(dynamic a, dynamic b) => a * b;
public static dynamic Div(dynamic a, dynamic b) => a / b;
public static dynamic Mod(dynamic a, dynamic b) => a % b;
public static dynamic LSh(dynamic a, dynamic b) => a << b;
public static dynamic RSh(dynamic a, dynamic b) => a >> b;
public static dynamic Xor(dynamic a, dynamic b) => a ^ b;
public static dynamic BitwiseAnd(dynamic a, dynamic b) => a & b;
public static dynamic BitwiseOr(dynamic a, dynamic b) => a | b;
public static dynamic GreaterThan(dynamic a, dynamic b) => a > b;
public static dynamic GreaterThanOrEquals(dynamic a, dynamic b) => a >= b;
public static dynamic LessThan(dynamic a, dynamic b) => a < b;
public static dynamic LessThanOrEquals(dynamic a, dynamic b) => a <= b;
public static dynamic Equal(dynamic a, dynamic b) => a == b;
public static dynamic NotEqual(dynamic a, dynamic b) => a != b;
public static dynamic UnaryNegate(dynamic a) => -a;
public static dynamic UnaryNot(dynamic a) => !a;
public static dynamic BitwiseNot(dynamic a) => ~a;
[UsedImplicitly] public static byte CastToByte(byte source) => source;
[UsedImplicitly] public static sbyte CastToSByte(byte source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(byte source) => source;
[UsedImplicitly] public static ushort CastToUInt16(byte source) => source;
[UsedImplicitly] public static int CastToInt32(byte source) => source;
[UsedImplicitly] public static uint CastToUInt32(byte source) => source;
[UsedImplicitly] public static long CastToInt64(byte source) => source;
[UsedImplicitly] public static ulong CastToUInt64(byte source) => source;
[UsedImplicitly] public static float CastToSingle(byte source) => source;
[UsedImplicitly] public static double CastToDouble(byte source) => source;
[UsedImplicitly] public static decimal CastToDecimal(byte source) => source;
[UsedImplicitly] public static char CastToChar(byte source) => (char)source;
[UsedImplicitly] public static byte CastToByte(sbyte source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(sbyte source) => source;
[UsedImplicitly] public static short CastToInt16(sbyte source) => source;
[UsedImplicitly] public static ushort CastToUInt16(sbyte source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(sbyte source) => source;
[UsedImplicitly] public static uint CastToUInt32(sbyte source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(sbyte source) => source;
[UsedImplicitly] public static ulong CastToUInt64(sbyte source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(sbyte source) => source;
[UsedImplicitly] public static double CastToDouble(sbyte source) => source;
[UsedImplicitly] public static decimal CastToDecimal(sbyte source) => source;
[UsedImplicitly] public static char CastToChar(sbyte source) => (char)source;
[UsedImplicitly] public static byte CastToByte(short source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(short source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(short source) => source;
[UsedImplicitly] public static ushort CastToUInt16(short source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(short source) => source;
[UsedImplicitly] public static uint CastToUInt32(short source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(short source) => source;
[UsedImplicitly] public static ulong CastToUInt64(short source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(short source) => source;
[UsedImplicitly] public static double CastToDouble(short source) => source;
[UsedImplicitly] public static decimal CastToDecimal(short source) => source;
[UsedImplicitly] public static char CastToChar(short source) => (char)source;
[UsedImplicitly] public static byte CastToByte(ushort source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(ushort source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(ushort source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(ushort source) => source;
[UsedImplicitly] public static int CastToInt32(ushort source) => source;
[UsedImplicitly] public static uint CastToUInt32(ushort source) => source;
[UsedImplicitly] public static long CastToInt64(ushort source) => source;
[UsedImplicitly] public static ulong CastToUInt64(ushort source) => source;
[UsedImplicitly] public static float CastToSingle(ushort source) => source;
[UsedImplicitly] public static double CastToDouble(ushort source) => source;
[UsedImplicitly] public static decimal CastToDecimal(ushort source) => source;
[UsedImplicitly] public static char CastToChar(ushort source) => (char)source;
[UsedImplicitly] public static byte CastToByte(int source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(int source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(int source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(int source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(int source) => source;
[UsedImplicitly] public static uint CastToUInt32(int source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(int source) => source;
[UsedImplicitly] public static ulong CastToUInt64(int source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(int source) => source;
[UsedImplicitly] public static double CastToDouble(int source) => source;
[UsedImplicitly] public static decimal CastToDecimal(int source) => source;
[UsedImplicitly] public static char CastToChar(int source) => (char)source;
[UsedImplicitly] public static byte CastToByte(uint source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(uint source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(uint source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(uint source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(uint source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(uint source) => source;
[UsedImplicitly] public static long CastToInt64(uint source) => source;
[UsedImplicitly] public static ulong CastToUInt64(uint source) => source;
[UsedImplicitly] public static float CastToSingle(uint source) => source;
[UsedImplicitly] public static double CastToDouble(uint source) => source;
[UsedImplicitly] public static decimal CastToDecimal(uint source) => source;
[UsedImplicitly] public static char CastToChar(uint source) => (char)source;
[UsedImplicitly] public static byte CastToByte(long source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(long source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(long source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(long source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(long source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(long source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(long source) => source;
[UsedImplicitly] public static ulong CastToUInt64(long source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(long source) => source;
[UsedImplicitly] public static double CastToDouble(long source) => source;
[UsedImplicitly] public static decimal CastToDecimal(long source) => source;
[UsedImplicitly] public static char CastToChar(long source) => (char)source;
[UsedImplicitly] public static byte CastToByte(ulong source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(ulong source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(ulong source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(ulong source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(ulong source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(ulong source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(ulong source) => (long)source;
[UsedImplicitly] public static ulong CastToUInt64(ulong source) => source;
[UsedImplicitly] public static float CastToSingle(ulong source) => source;
[UsedImplicitly] public static double CastToDouble(ulong source) => source;
[UsedImplicitly] public static decimal CastToDecimal(ulong source) => source;
[UsedImplicitly] public static char CastToChar(ulong source) => (char)source;
[UsedImplicitly] public static byte CastToByte(float source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(float source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(float source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(float source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(float source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(float source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(float source) => (long)source;
[UsedImplicitly] public static ulong CastToUInt64(float source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(float source) => source;
[UsedImplicitly] public static double CastToDouble(float source) => source;
[UsedImplicitly] public static decimal CastToDecimal(float source) => (decimal)source;
[UsedImplicitly] public static char CastToChar(float source) => (char)source;
[UsedImplicitly] public static byte CastToByte(double source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(double source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(double source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(double source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(double source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(double source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(double source) => (long)source;
[UsedImplicitly] public static ulong CastToUInt64(double source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(double source) => (float)source;
[UsedImplicitly] public static double CastToDouble(double source) => source;
[UsedImplicitly] public static decimal CastToDecimal(double source) => (decimal)source;
[UsedImplicitly] public static char CastToChar(double source) => (char)source;
[UsedImplicitly] public static byte CastToByte(decimal source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(decimal source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(decimal source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(decimal source) => (ushort)source;
[UsedImplicitly] public static int CastToInt32(decimal source) => (int)source;
[UsedImplicitly] public static uint CastToUInt32(decimal source) => (uint)source;
[UsedImplicitly] public static long CastToInt64(decimal source) => (long)source;
[UsedImplicitly] public static ulong CastToUInt64(decimal source) => (ulong)source;
[UsedImplicitly] public static float CastToSingle(decimal source) => (float)source;
[UsedImplicitly] public static double CastToDouble(decimal source) => (double)source;
[UsedImplicitly] public static decimal CastToDecimal(decimal source) => source;
[UsedImplicitly] public static char CastToChar(decimal source) => (char)source;
[UsedImplicitly] public static byte CastToByte(char source) => (byte)source;
[UsedImplicitly] public static sbyte CastToSByte(char source) => (sbyte)source;
[UsedImplicitly] public static short CastToInt16(char source) => (short)source;
[UsedImplicitly] public static ushort CastToUInt16(char source) => source;
[UsedImplicitly] public static int CastToInt32(char source) => source;
[UsedImplicitly] public static uint CastToUInt32(char source) => source;
[UsedImplicitly] public static long CastToInt64(char source) => source;
[UsedImplicitly] public static ulong CastToUInt64(char source) => source;
[UsedImplicitly] public static float CastToSingle(char source) => source;
[UsedImplicitly] public static double CastToDouble(char source) => source;
[UsedImplicitly] public static decimal CastToDecimal(char source) => source;
[UsedImplicitly] public static char CastToChar(char source) => source;
}
public static BoundConstantExpression FoldConstantBinaryExpression(
BindContext context,
BinaryExpressionSyntax syntax,
MethodSymbol binaryOperator,
BoundExpression lhs, BoundExpression rhs)
{
// Value type + null comparison which will always be false for == and true for !=
// This folding is needed for null comparisons in generics to work as expected
if ((lhs.ValueType.IsValueType && rhs.IsConstant && rhs.ConstantValue.Value == null) ||
(rhs.ValueType.IsValueType && lhs.IsConstant && lhs.ConstantValue.Value == null))
{
if (syntax.OperatorToken.Kind() == SyntaxKind.EqualsEqualsToken)
return new BoundConstantExpression(new ConstantValue<bool>(false),
context.GetTypeSymbol(typeof(bool)), syntax);
if (syntax.OperatorToken.Kind() == SyntaxKind.ExclamationEqualsToken)
return new BoundConstantExpression(new ConstantValue<bool>(true),
context.GetTypeSymbol(typeof(bool)), syntax);
}
if (!lhs.IsConstant || !rhs.IsConstant)
return null;
if (binaryOperator == null || (binaryOperator.IsOperator && binaryOperator.IsExtern))
{
object foldedValue;
object lhsValue = lhs.ConstantValue.Value;
object rhsValue = rhs.ConstantValue.Value;
switch (syntax.OperatorToken.Kind())
{
case SyntaxKind.PlusToken:
foldedValue = DynamicInvoke.Add(lhsValue, rhsValue);
break;
case SyntaxKind.MinusToken:
foldedValue = DynamicInvoke.Sub(lhsValue, rhsValue);
break;
case SyntaxKind.AsteriskToken:
foldedValue = DynamicInvoke.Mul(lhsValue, rhsValue);
break;
case SyntaxKind.SlashToken:
foldedValue = DynamicInvoke.Div(lhsValue, rhsValue);
break;
case SyntaxKind.PercentToken:
foldedValue = DynamicInvoke.Mod(lhsValue, rhsValue);
break;
case SyntaxKind.LessThanLessThanToken:
foldedValue = DynamicInvoke.LSh(lhsValue, rhsValue);
break;
case SyntaxKind.GreaterThanGreaterThanToken:
foldedValue = DynamicInvoke.RSh(lhsValue, rhsValue);
break;
case SyntaxKind.CaretToken:
foldedValue = DynamicInvoke.Xor(lhsValue, rhsValue);
break;
case SyntaxKind.AmpersandToken:
case SyntaxKind.AmpersandAmpersandToken: // When we're dealing with constants short circuiting shouldn't matter
foldedValue = DynamicInvoke.BitwiseAnd(lhsValue, rhsValue);
break;
case SyntaxKind.BarToken:
case SyntaxKind.BarBarToken:
foldedValue = DynamicInvoke.BitwiseOr(lhsValue, rhsValue);
break;
case SyntaxKind.GreaterThanToken:
foldedValue = DynamicInvoke.GreaterThan(lhsValue, rhsValue);
break;
case SyntaxKind.GreaterThanEqualsToken:
foldedValue = DynamicInvoke.GreaterThanOrEquals(lhsValue, rhsValue);
break;
case SyntaxKind.LessThanToken:
foldedValue = DynamicInvoke.LessThan(lhsValue, rhsValue);
break;
case SyntaxKind.LessThanOrEqualExpression:
foldedValue = DynamicInvoke.LessThanOrEquals(lhsValue, rhsValue);
break;
case SyntaxKind.EqualsEqualsToken:
foldedValue = DynamicInvoke.Equal(lhsValue, rhsValue);
break;
case SyntaxKind.ExclamationEqualsToken:
foldedValue = DynamicInvoke.NotEqual(lhsValue, rhsValue);
break;
default:
return null;
}
IConstantValue constantValue = (IConstantValue)Activator.CreateInstance(typeof(ConstantValue<>).MakeGenericType(foldedValue.GetType()), foldedValue);
return new BoundConstantExpression(constantValue, context.GetTypeSymbol(foldedValue.GetType()), syntax);
}
return null;
}
public static BoundConstantExpression FoldConstantUnaryPrefixExpression(
BindContext context,
PrefixUnaryExpressionSyntax syntax,
MethodSymbol unaryOperator,
BoundExpression operand)
{
if (!operand.IsConstant)
return null;
if (unaryOperator.IsOperator && unaryOperator.IsExtern)
{
object foldedValue;
object operandValue = operand.ConstantValue.Value;
switch (syntax.OperatorToken.Kind())
{
case SyntaxKind.MinusToken:
foldedValue = DynamicInvoke.UnaryNegate(operandValue);
break;
case SyntaxKind.ExclamationToken:
foldedValue = DynamicInvoke.UnaryNot(operandValue);
break;
case SyntaxKind.TildeToken:
foldedValue = DynamicInvoke.BitwiseNot(operandValue);
break;
default:
return null;
}
IConstantValue constantValue = (IConstantValue)Activator.CreateInstance(typeof(ConstantValue<>).MakeGenericType(foldedValue.GetType()), foldedValue);
return new BoundConstantExpression(constantValue, context.GetTypeSymbol(foldedValue.GetType()), syntax);
}
return null;
}
private static readonly HashSet<Type> _convertableConstantTypes = new HashSet<Type>
{
typeof(byte),
typeof(sbyte),
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong),
typeof(float),
typeof(double),
typeof(decimal),
typeof(char),
// It seems like mono doesn't like dynamic methods on non-base types. We'll disallow these for a while and see if it stops crashing.
// todo: add to generated cast method allowed types
// typeof(Vector2),
// typeof(Vector3),
// typeof(Vector4),
};
public static bool CanDoConstantConversion(Type type)
{
return _convertableConstantTypes.Contains(type);
}
private static Dictionary<(Type, Type), MethodInfo> _typeFoldLookup;
private static Dictionary<(Type, Type), MethodInfo> GetFoldingLookup()
{
if (_typeFoldLookup != null)
return _typeFoldLookup;
Dictionary<(Type, Type), MethodInfo> newLookup = new Dictionary<(Type, Type), MethodInfo>();
foreach (Type sourceType in _convertableConstantTypes)
{
foreach (Type targetType in _convertableConstantTypes)
{
string methodName = $"CastTo{targetType.Name}";
MethodInfo castMethod = typeof(DynamicInvoke).GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(m => m.Name == methodName && m.GetParameters()[0].ParameterType == sourceType);
newLookup.Add((sourceType, targetType), castMethod);
}
}
_typeFoldLookup = newLookup;
return _typeFoldLookup;
}
public static object FoldConstantConversion(Type targetType, object value)
{
if (value == null)
return null;
if (targetType == value.GetType())
return value;
if (value.GetType().IsEnum && UdonSharpUtils.IsIntegerType(targetType))
{
MethodInfo enumToInt = typeof(Convert).GetMethods().First(e =>
e.Name == $"To{targetType.Name}" && e.GetParameters()[0].ParameterType == typeof(object));
return enumToInt.Invoke(null, new [] { value });
}
GetFoldingLookup().TryGetValue((value.GetType(), targetType), out var castMethod);
if (castMethod != null)
return castMethod.Invoke(null, new [] {value});
throw new NotImplementedException("Cannot cast to type " + targetType);
}
}
}

Some files were not shown because too many files have changed in this diff Show More