private static void InternalConvertFromFunction( CodeTextWriter tw, IExtractContextHost extractContext, PreparedMethodInformation preparedMethod, DebugInformationOptions debugInformationOption) { var locals = preparedMethod.Method.LocalVariables; tw.SplitLine(); tw.WriteLine("///////////////////////////////////////"); tw.WriteLine( "// [3] {0}{1}", preparedMethod.Method.IsVirtual ? "Virtual: " : string.Empty, preparedMethod.Method.FriendlyName); var codeStream = preparedMethod.Method.CodeStream; // Write exception filters: if (codeStream.ExceptionHandlers.Length >= 1) { tw.SplitLine(); tw.WriteLine("//-------------------"); tw.WriteLine("// [3-1] Exception filters:"); tw.SplitLine(); for (var handlerIndex = 0; handlerIndex < codeStream.ExceptionHandlers.Length; handlerIndex++) { var handler = codeStream.ExceptionHandlers[handlerIndex]; var filterName = string.Format( "{0}_ExceptionFilter{1}__", preparedMethod.Method.CLanguageFunctionName, handlerIndex); tw.WriteLine( "static int16_t {0}(System_Exception* ex)", filterName); tw.WriteLine("{"); using (var _ = tw.Shift()) { tw.WriteLine("il2c_assert(ex != NULL);"); for (var catchHandlerIndex = 0; catchHandlerIndex < handler.CatchHandlers.Length; catchHandlerIndex++) { var catchHandler = handler.CatchHandlers[catchHandlerIndex]; if (catchHandler.CatchHandlerType == ExceptionCatchHandlerTypes.Catch) { tw.WriteLine( "if (il2c_isinst__(ex, il2c_typeof({0}))) return {1};", catchHandler.CatchType.MangledName, catchHandlerIndex + 1); } } // Write finally block index if contains. var finallyHandler = handler.CatchHandlers. Select((catchHandler, index) => new { catchHandler, index }). FirstOrDefault(entry => entry.catchHandler.CatchHandlerType == ExceptionCatchHandlerTypes.Finally); if (finallyHandler != null) { tw.WriteLine("return IL2C_FILTER_FINALLY; // Not matched (will go to finally)"); } else { tw.WriteLine("return IL2C_FILTER_NOMATCH; // Not matched"); } } tw.WriteLine("}"); } } // Start function: tw.SplitLine(); tw.WriteLine("//-------------------"); tw.WriteLine("// [3-2] Function body:"); tw.SplitLine(); tw.WriteLine(preparedMethod.Method.CLanguageFunctionPrototype); tw.WriteLine("{"); using (var _ = tw.Shift()) { if (!preparedMethod.Method.IsStatic) { tw.WriteLine("il2c_assert(this__ != NULL);"); tw.SplitLine(); } var localDefinitions = preparedMethod.Method.LocalVariables. Where(local => !local.TargetType.IsReferenceType). ToArray(); if (localDefinitions.Length >= 1) { tw.WriteLine("//-------------------"); tw.WriteLine("// [3-3] Local variables (not objref):"); tw.SplitLine(); foreach (var local in localDefinitions) { // HACK: The local variables mark to "volatile." // Because the gcc misread these variables calculated statically (or maybe assigned to the registers) // at compile time with optimization. // It will cause the strange results for exception handling (with sjlj.) tw.WriteLine( "{0}{1} {2};", (codeStream.ExceptionHandlers.Length >= 1) ? "volatile " : string.Empty, local.TargetType.CLanguageTypeName, extractContext.GetSymbolName(local)); } tw.SplitLine(); } var stackDefinitions = preparedMethod.Stacks. Where(stack => !stack.TargetType.IsReferenceType). ToArray(); if (stackDefinitions.Length >= 1) { tw.WriteLine("//-------------------"); tw.WriteLine("// [3-4] Evaluation stacks (not objref):"); tw.SplitLine(); foreach (var stack in stackDefinitions) { tw.WriteLine( "{0} {1};", stack.TargetType.CLanguageTypeName, extractContext.GetSymbolName(stack)); } tw.SplitLine(); } var objRefEntries = locals. Concat(preparedMethod.Stacks). Where(v => v.TargetType.IsReferenceType). // Only objref ToArray(); if (objRefEntries.Length >= 1) { tw.WriteLine("//-------------------"); tw.WriteLine("// [3-5] Setup execution frame:"); tw.SplitLine(); tw.WriteLine( "struct {0}_EXECUTION_FRAME__", preparedMethod.Method.CLanguageFunctionName); tw.WriteLine("{"); using (var __ = tw.Shift()) { tw.WriteLine("uint8_t objRefCount__;"); tw.WriteLine("uint8_t objRefRefCount__;"); tw.WriteLine("IL2C_EXECUTION_FRAME* pNext__;"); foreach (var objRefEntry in objRefEntries) { tw.WriteLine( "{0} {1};", objRefEntry.TargetType.CLanguageTypeName, extractContext.GetSymbolName(objRefEntry)); } } // Important NULL assigner (p = NULL): // Because these variables are pointer (of object reference 'O' type). // So GC will traverse these variables just setup the stack frame. // TODO: https://github.com/kekyo/IL2C/issues/12 tw.WriteLine("}} frame__ = {{ {0}, 0 }};", objRefEntries.Length); tw.WriteLine("il2c_link_execution_frame(&frame__);"); tw.SplitLine(); } tw.WriteLine("//-------------------"); tw.WriteLine("// [3-6] IL body:"); tw.SplitLine(); // Set symbol prefix to make valid access variables. using (var __ = extractContext.BeginLocalVariablePrefix( local => local.TargetType.IsReferenceType ? "frame__." : null)) { // Construct exception handler controller. var exceptionHandlerController = new ExceptionHandlerController( codeStream.ExceptionHandlers, (handler, handlerIndex, nestedIndex) => { var nestedIndexName = string.Format("nest{0}", nestedIndex); extractContext.SetNestedExceptionFrameIndexName(nestedIndexName); // Reached try block: var filterName = string.Format( "{0}_ExceptionFilter{1}__", preparedMethod.Method.CLanguageFunctionName, handlerIndex); tw.WriteLine("il2c_try({0}, {1})", nestedIndexName, filterName); tw.WriteLine("{"); tw.Shift(); }, (handler, handlerIndex, nestedIndex) => { // Reached try end block: tw.Shift(-1); tw.WriteLine("}"); }, (handler, handlerIndex, nestedIndex, catchHandler, catchHandlerIndex) => { var nestedIndexName = extractContext.GetExceptionNestedFrameIndexName(); switch (catchHandler.CatchHandlerType) { case ExceptionCatchHandlerTypes.Catch: // Reached catch block: tw.WriteLine( "il2c_catch({0}, {1}, {2}) // catch ({3})", nestedIndexName, catchHandlerIndex + 1, extractContext.GetSymbolName(preparedMethod.CatchVariables[catchHandler.CatchStart]), catchHandler.CatchType.MangledName); break; case ExceptionCatchHandlerTypes.Finally: // Reached finally block: tw.WriteLine("il2c_finally({0})", nestedIndexName); break; } tw.WriteLine("{"); tw.Shift(); }, (handler, handlerIndex, nestedIndex, catchHandler, catchHandlerIndex) => { // Reached catch end block: tw.Shift(-1); tw.WriteLine("}"); }, (handler, handlerIndex, nestedIndex, parentHandler, parentHandlerIndex, parentNestedIndex) => { var nestedIndexName = extractContext.GetExceptionNestedFrameIndexName(); var parentNestedIndexName = (parentNestedIndex >= 0) ? string.Format("nest{0}", parentNestedIndex) : null; // Write leave bind expressions if needed. // Extract the continuation fromOffset inside at mostly inner exception handler. var bindEntries = preparedMethod.LeaveContinuations. SelectMany(entry => codeStream.ExceptionHandlers. // nested exception handlers: inner --> outer Reverse(). // Is this handler contains leave continuation target? Where(h => entry.Value.fromOffsets.Any(offset => h.ContainsOffset(offset))). // Found. Select(h => new { handler = h, continuationIndex = entry.Key, entry.Value.targetOffset })). // ... is current handler? Where(entry => entry.handler.Equals(handler)). ToArray(); if (bindEntries.Length >= 1) { tw.WriteLine("il2c_leave_to({0})", nestedIndexName); tw.WriteLine("{"); using (var ___ = tw.Shift()) { foreach (var bind in bindEntries) { if ((parentNestedIndex < 0) || codeStream.ExceptionHandlers[parentNestedIndex].ContainsOffset(bind.targetOffset)) { var labelName = preparedMethod.LabelNames[bind.targetOffset]; tw.WriteLine( "il2c_leave_bind({0}, {1}, {2});", nestedIndexName, bind.continuationIndex, labelName); } else { tw.WriteLine( "il2c_leave_through({0}, {1}, {2});", nestedIndexName, bind.continuationIndex, parentNestedIndexName); } } } tw.WriteLine("}"); } // Reached end of entire try block. tw.WriteLine("il2c_end_try({0});", nestedIndexName); extractContext.SetNestedExceptionFrameIndexName(parentNestedIndexName); }); // Traverse code fragments. var canWriteSequencePoint = true; foreach (var ci in codeStream) { // 1: Update the exception handler controller. // (Will write exception related sentences.) exceptionHandlerController.Update(ci); // 2: Write label if available and used. if (preparedMethod.LabelNames.TryGetValue(ci.Offset, out var labelName)) { using (var ___ = tw.Shift(-1)) { tw.WriteLine("{0}:", labelName); } } // 3: Write the line preprocessor directive if available. if (canWriteSequencePoint && ci.Debug.Any()) { var sp = ci.Debug.First(); switch (debugInformationOption) { case DebugInformationOptions.Full: tw.Parent.WriteLine( "#line {0} \"{1}\"", sp.Line, sp.Path.Replace("\\", "\\\\")); break; case DebugInformationOptions.CommentOnly: tw.Parent.WriteLine( "/* {0}({1}): */", sp.Path.Replace("\\", "\\\\"), sp.Line); break; } canWriteSequencePoint = false; } // 4: Generate source code fragments and write. if (debugInformationOption != DebugInformationOptions.None) { // Write debugging information. tw.WriteLine( "/* {0} */", ci); } var sourceCodes = preparedMethod.Generators[ci.Offset](extractContext); foreach (var sourceCode in sourceCodes) { // Dirty hack: // Write unlink execution frame code if cause exiting method. if (sourceCode.StartsWith("return") && (objRefEntries.Length >= 1)) { tw.WriteLine( "il2c_unlink_execution_frame(&frame__);"); } tw.WriteLine( "{0};", sourceCode); canWriteSequencePoint = true; } } if (!exceptionHandlerController.IsFinished) { throw new InvalidProgramSequenceException( "Invalid exception handler range. MethodName={0}, ExceptionHandlers=[{1}]", preparedMethod.Method.FriendlyName, string.Join( ",", codeStream.ExceptionHandlers. Select(handler => string.Format("[{0}]", handler)))); } } } tw.WriteLine("}"); tw.SplitLine(); }
private static void InternalConvertFromFunction( CodeTextWriter tw, IExtractContextHost extractContext, PreparedMethodInformation preparedMethod, DebugInformationOptions debugInformationOption) { var locals = preparedMethod.Method.LocalVariables; tw.WriteLine("///////////////////////////////////////"); tw.WriteLine( "// [3] {0}{1}", preparedMethod.Method.IsVirtual ? "Virtual: " : string.Empty, preparedMethod.Method.FriendlyName); tw.SplitLine(); var codeStream = preparedMethod.Method.CodeStream; var objRefEntries = locals. Concat(preparedMethod.Stacks). Where(v => v.TargetType.IsReferenceType). // Only objref ToArray(); var valueEntries = locals. Concat(preparedMethod.Stacks). Where(v => v.TargetType.IsValueType && v.TargetType.IsRequiredTraverse). ToArray(); // Write declaring exception handlers if (codeStream.ExceptionHandlers.Length >= 1) { InternalConvertExceptionFilter( tw, extractContext, preparedMethod, codeStream); } // Write declaring execution frame if ((objRefEntries.Length >= 1) || (valueEntries.Length >= 1)) { InternalConvertExecutionFrame( tw, extractContext, preparedMethod, objRefEntries, valueEntries); } // Make readable debugging comment, it was splitting and reducing same informations. var debugInformationController = new DebugInformationWriteController( preparedMethod.Method, debugInformationOption); // Start function: tw.WriteLine("//-------------------"); tw.WriteLine("// [3-2] Function body:"); tw.SplitLine(); tw.WriteLine(preparedMethod.Method.CLanguageFunctionPrototype); tw.WriteLine("{"); using (var _ = tw.Shift()) { if (!preparedMethod.Method.IsStatic) { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("il2c_assert(this__ != NULL);"); tw.SplitLine(); } var localDefinitions = preparedMethod.Method.LocalVariables. Where(local => !local.TargetType.IsReferenceType). ToArray(); if (localDefinitions.Length >= 1) { tw.WriteLine("//-------------------"); tw.WriteLine("// [3-3] Local variables (!objref):"); tw.SplitLine(); foreach (var local in localDefinitions) { var name = extractContext.GetSymbolName(local); // HACK: The local variables mark to "volatile." // Because the gcc misread these variables calculated statically (or maybe assigned to the registers) // at compile time with optimization. // It will cause the strange results for exception handling (with sjlj.) // We have to initialize the local variables. if (local.TargetType.IsPrimitive || local.TargetType.IsPointer || local.TargetType.IsByReference) { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "{0}{1} {2} = {3};", (codeStream.ExceptionHandlers.Length >= 1) ? "volatile " : string.Empty, local.TargetType.CLanguageTypeName, name, Utilities.GetCLanguageExpression(local.TargetType.InternalStaticEmptyValue)); } else { Debug.Assert(local.TargetType.IsValueType); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "{0}{1} {2};", (codeStream.ExceptionHandlers.Length >= 1) ? "volatile " : string.Empty, local.TargetType.CLanguageTypeName, name); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "memset({0}&{1}, 0x00, sizeof {1});", (codeStream.ExceptionHandlers.Length >= 1) ? "(void*)" : string.Empty, name); } } tw.SplitLine(); } var stackDefinitions = preparedMethod.Stacks. Where(stack => !stack.TargetType.IsReferenceType). ToArray(); if (stackDefinitions.Length >= 1) { tw.WriteLine("//-------------------"); tw.WriteLine("// [3-4] Evaluation stacks (!objref):"); tw.SplitLine(); foreach (var stack in stackDefinitions) { var name = extractContext.GetSymbolName(stack); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "{0} {1};", stack.TargetType.CLanguageTypeName, name); // Note: We often don't have to initalize the evaluation stack variables. // Because these variables push value at first usage. // But the value type may contains objref field, // so we have to initialize for value type. if (stack.TargetType.IsRequiredTraverse) { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "memset(&{0}, 0, sizeof {0});", name); } } tw.SplitLine(); } // Write doing setup execution frame if ((objRefEntries.Length >= 1) || (valueEntries.Length >= 1)) { InternalConvertSetupExecutionFrame( tw, extractContext, preparedMethod, objRefEntries, valueEntries, debugInformationController); } tw.WriteLine("//-------------------"); tw.WriteLine("// [3-6] IL body:"); tw.SplitLine(); // Set symbol prefix to make valid access variables. using (var __ = extractContext.BeginLocalVariablePrefix( local => local.TargetType.IsReferenceType ? "frame__." : null)) { // Construct exception handler controller. var exceptionHandlerController = new ExceptionHandlerController( codeStream.ExceptionHandlers, (handler, handlerIndex, nestedIndex) => { var nestedIndexName = string.Format("nest{0}", nestedIndex); extractContext.SetNestedExceptionFrameIndexName(nestedIndexName); // Reached try block: var filterName = string.Format( "{0}_ExceptionFilter{1}__", preparedMethod.Method.CLanguageFunctionName, handlerIndex); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("il2c_try({0}, {1})", nestedIndexName, filterName); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("{"); tw.Shift(); }, (handler, handlerIndex, nestedIndex) => { // Reached try end block: debugInformationController.WriteInformationBeforeCode(tw); tw.Shift(-1); tw.WriteLine("}"); }, (handler, handlerIndex, nestedIndex, catchHandler, catchHandlerIndex) => { var nestedIndexName = extractContext.GetExceptionNestedFrameIndexName(); switch (catchHandler.CatchHandlerType) { case ExceptionCatchHandlerTypes.Catch: // Reached catch block: debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "il2c_catch({0}, {1}, {2}) // catch ({3})", nestedIndexName, catchHandlerIndex + 1, extractContext.GetSymbolName(preparedMethod.CatchVariables[catchHandler.CatchStart]), catchHandler.CatchType.MangledUniqueName); break; case ExceptionCatchHandlerTypes.Finally: // Reached finally block: debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("il2c_finally({0})", nestedIndexName); break; } debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("{"); tw.Shift(); }, (handler, handlerIndex, nestedIndex, catchHandler, catchHandlerIndex) => { // Reached catch end block: debugInformationController.WriteInformationBeforeCode(tw); tw.Shift(-1); tw.WriteLine("}"); }, (handler, handlerIndex, nestedIndex, parentHandler, parentHandlerIndex, parentNestedIndex) => { var nestedIndexName = extractContext.GetExceptionNestedFrameIndexName(); var parentNestedIndexName = (parentNestedIndex >= 0) ? string.Format("nest{0}", parentNestedIndex) : null; // Write leave bind expressions if needed. // Extract the continuation fromOffset inside at mostly inner exception handler. var bindEntries = preparedMethod.LeaveContinuations. SelectMany(entry => codeStream.ExceptionHandlers. // nested exception handlers: inner --> outer Reverse(). // Is this handler contains leave continuation target? Where(h => entry.Value.fromOffsets.Any(offset => h.ContainsOffset(offset))). // Found. Select(h => new { handler = h, continuationIndex = entry.Key, entry.Value.targetOffset })). // ... is current handler? Where(entry => entry.handler.Equals(handler)). ToArray(); if (bindEntries.Length >= 1) { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("il2c_leave_to({0})", nestedIndexName); debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("{"); using (var ___ = tw.Shift()) { foreach (var bind in bindEntries) { if ((parentNestedIndex < 0) || codeStream.ExceptionHandlers[parentNestedIndex].ContainsOffset(bind.targetOffset)) { var labelName = preparedMethod.LabelNames[bind.targetOffset]; debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "il2c_leave_bind({0}, {1}, {2});", nestedIndexName, bind.continuationIndex, labelName); } else { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "il2c_leave_through({0}, {1}, {2});", nestedIndexName, bind.continuationIndex, parentNestedIndexName); } } } debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("}"); } // Reached end of entire try block. debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("il2c_end_try({0});", nestedIndexName); extractContext.SetNestedExceptionFrameIndexName(parentNestedIndexName); }); // Traverse code fragments. foreach (var ci in codeStream) { debugInformationController.SetNextCode(ci); // 1: Update the exception handler controller. // (Will write exception related sentences.) exceptionHandlerController.Update(ci); // 2: Write label if available and used. if (preparedMethod.LabelNames.TryGetValue(ci.Offset, out var labelName)) { using (var ___ = tw.Shift(-1)) { tw.WriteLine("{0}:", labelName); } } // 3: Write source code comment. debugInformationController.WriteCodeComment(tw); // 4: Generate source code fragments and write. var sourceCodes = preparedMethod.Generators[ci.Offset](extractContext); foreach (var sourceCode in sourceCodes) { // DIRTY HACK: // Write unlink execution frame code if cause exiting method. if (sourceCode.StartsWith("return") && ((objRefEntries.Length >= 1) || (valueEntries.Length >= 1))) { debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "il2c_unlink_execution_frame(&frame__);"); } debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine( "{0};", sourceCode); } } // If last opcode is 'endfinally' and not emitted 'ret', // can't finished for exceptionHandlerController. // We can check and force update the TryFinish method for this situation. exceptionHandlerController.TryFinish(); if (!exceptionHandlerController.IsFinished) { throw new InvalidProgramSequenceException( "Invalid exception handler range. MethodName={0}, ExceptionHandlers=[{1}]", preparedMethod.Method.FriendlyName, string.Join( ",", codeStream.ExceptionHandlers. Select(handler => string.Format("[{0}]", handler)))); } } } debugInformationController.WriteInformationBeforeCode(tw); tw.WriteLine("}"); tw.SplitLine(); }