diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index a301e5d5119d39..f924f2f8db361c 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -604,6 +604,37 @@ int32_t* InterpCompiler::EmitCodeIns(int32_t *ip, InterpInst *ins, TArraydata[0]; + InterpType fType = (InterpType)ins->data[1]; + int32_t fSize = ins->data[2]; + // Revert opcode emit + ip--; + + int destOffset = m_pVars[ins->dVar].offset; + int srcOffset = m_pVars[ins->sVars[0]].offset; + srcOffset += fOffset; + if (fSize) + opcode = INTOP_MOV_VT; + else + opcode = InterpGetMovForType(fType, true); + *ip++ = opcode; + *ip++ = destOffset; + *ip++ = srcOffset; + if (opcode == INTOP_MOV_VT) + *ip++ = fSize; + } + else if (opcode == INTOP_LDLOCA) + { + // This opcode references a var, int sVars[0], but it is not registered as a source for it + // aka g_interpOpSVars[INTOP_LDLOCA] is 0. + *ip++ = m_pVars[ins->dVar].offset; + *ip++ = m_pVars[ins->sVars[0]].offset; + } else { // Default code emit for an instruction. The opcode was already emitted above. @@ -672,6 +703,9 @@ void InterpCompiler::EmitCode() m_pCBB = bb; for (InterpInst *ins = bb->pFirstIns; ins != NULL; ins = ins->pNext) { + if (InterpOpIsEmitNop(ins->opcode)) + continue; + ip = EmitCodeIns(ip, ins, &relocs); } } @@ -1350,29 +1384,46 @@ bool InterpCompiler::EmitCallIntrinsics(CORINFO_METHOD_HANDLE method, CORINFO_SI const char *className = NULL; const char *namespaceName = NULL; const char *methodName = m_compHnd->getMethodNameFromMetadata(method, &className, &namespaceName, NULL, 0); - int32_t opcode = -1; if (namespaceName && !strcmp(namespaceName, "System")) { if (className && !strcmp(className, "Environment")) { if (methodName && !strcmp(methodName, "FailFast")) - opcode = INTOP_FAILFAST; // to be removed, not really an intrisic + { + AddIns(INTOP_FAILFAST); // to be removed, not really an intrisic + m_pStackPointer--; + return true; + } + } + else if (className && !strcmp(className, "Object")) + { + // This is needed at this moment because we don't have support for interop + // with compiled code, but it might make sense in the future for this to remain + // in order to avoid redundant interp to jit transition. + if (methodName && !strcmp(methodName, ".ctor")) + { + AddIns(INTOP_NOP); + m_pStackPointer--; + return true; + } } - } - - if (opcode != -1) - { - AddIns(opcode); - return true; } return false; } -void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readonly, bool tailcall) +void InterpCompiler::ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken) +{ + pResolvedToken->tokenScope = m_compScopeHnd; + pResolvedToken->tokenContext = METHOD_BEING_COMPILED_CONTEXT(); + pResolvedToken->token = token; + pResolvedToken->tokenType = tokenKind; + m_compHnd->resolveToken(pResolvedToken); +} + +CORINFO_METHOD_HANDLE InterpCompiler::ResolveMethodToken(uint32_t token) { - uint32_t token = getU4LittleEndian(m_ip + 1); CORINFO_RESOLVED_TOKEN resolvedToken; resolvedToken.tokenScope = m_compScopeHnd; @@ -1381,7 +1432,14 @@ void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readon resolvedToken.tokenType = CORINFO_TOKENKIND_Method; m_compHnd->resolveToken(&resolvedToken); - CORINFO_METHOD_HANDLE targetMethod = resolvedToken.hMethod; + return resolvedToken.hMethod; +} + +void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readonly, bool tailcall) +{ + uint32_t token = getU4LittleEndian(m_ip + 1); + + CORINFO_METHOD_HANDLE targetMethod = ResolveMethodToken(token); CORINFO_SIG_INFO targetSignature; m_compHnd->getMethodSig(targetMethod, &targetSignature); @@ -1441,10 +1499,188 @@ void InterpCompiler::EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readon m_ip += 5; } +static int32_t GetLdindForType(InterpType interpType) +{ + switch (interpType) { + case InterpTypeI1: return INTOP_LDIND_I1; + case InterpTypeU1: return INTOP_LDIND_U1; + case InterpTypeI2: return INTOP_LDIND_I2; + case InterpTypeU2: return INTOP_LDIND_U2; + case InterpTypeI4: return INTOP_LDIND_I4; + case InterpTypeI8: return INTOP_LDIND_I8; + case InterpTypeR4: return INTOP_LDIND_R4; + case InterpTypeR8: return INTOP_LDIND_R8; + case InterpTypeO: return INTOP_LDIND_I; + case InterpTypeVT: return INTOP_LDIND_VT; + case InterpTypeByRef: return INTOP_LDIND_I; + default: + assert(0); + } + return -1; +} + +static int32_t GetStindForType(InterpType interpType) +{ + switch (interpType) { + case InterpTypeI1: return INTOP_STIND_I1; + case InterpTypeU1: return INTOP_STIND_U1; + case InterpTypeI2: return INTOP_STIND_I2; + case InterpTypeU2: return INTOP_STIND_U2; + case InterpTypeI4: return INTOP_STIND_I4; + case InterpTypeI8: return INTOP_STIND_I8; + case InterpTypeR4: return INTOP_STIND_R4; + case InterpTypeR8: return INTOP_STIND_R8; + case InterpTypeO: return INTOP_STIND_I; + case InterpTypeVT: return INTOP_STIND_VT; + case InterpTypeByRef: return INTOP_STIND_I; + default: + assert(0); + } + return -1; +} + +void InterpCompiler::EmitLdind(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int32_t offset) +{ + // Address is at the top of the stack + m_pStackPointer--; + int32_t opcode = GetLdindForType(interpType); + AddIns(opcode); + m_pLastIns->SetSVar(m_pStackPointer[0].var); + m_pLastIns->data[0] = offset; + if (interpType == InterpTypeVT) + { + int size = m_compHnd->getClassSize(clsHnd); + m_pLastIns->data[1] = size; + PushTypeVT(clsHnd, size); + } + else + { + PushInterpType(interpType, NULL); + } + m_pLastIns->SetDVar(m_pStackPointer[-1].var); +} + +void InterpCompiler::EmitStind(InterpType interpType, CORINFO_CLASS_HANDLE clsHnd, int32_t offset, bool reverseSVarOrder) +{ + // stack contains address and then the value to be stored + // or in the reverse order if the flag is set + if (interpType == InterpTypeVT) + { + if (m_compHnd->getClassAttribs(clsHnd) & CORINFO_FLG_CONTAINS_GC_PTR) + { + AddIns(INTOP_STIND_VT); + m_pLastIns->data[1] = GetDataItemIndex(clsHnd); + } + else + { + AddIns(INTOP_STIND_VT_NOREF); + m_pLastIns->data[1] = m_compHnd->getClassSize(clsHnd); + } + } + else + { + AddIns(GetStindForType(interpType)); + } + + m_pLastIns->data[0] = offset; + + m_pStackPointer -= 2; + if (reverseSVarOrder) + m_pLastIns->SetSVars2(m_pStackPointer[1].var, m_pStackPointer[0].var); + else + m_pLastIns->SetSVars2(m_pStackPointer[0].var, m_pStackPointer[1].var); + +} + +void InterpCompiler::EmitStaticFieldAddress(CORINFO_FIELD_INFO *pFieldInfo, CORINFO_RESOLVED_TOKEN *pResolvedToken) +{ + switch (pFieldInfo->fieldAccessor) + { + case CORINFO_FIELD_STATIC_ADDRESS: + case CORINFO_FIELD_STATIC_RVA_ADDRESS: + { + // const field address + assert(pFieldInfo->fieldLookup.accessType == IAT_VALUE); + AddIns(INTOP_LDPTR); + PushInterpType(InterpTypeByRef, NULL); + m_pLastIns->SetDVar(m_pStackPointer[-1].var); + m_pLastIns->data[0] = GetDataItemIndex(pFieldInfo->fieldLookup.addr); + break; + } + case CORINFO_FIELD_STATIC_TLS_MANAGED: + case CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER: + { + void *helperArg = NULL; + switch (pFieldInfo->helper) + { + case CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE_NOCTOR_OPTIMIZED: + case CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE_NOCTOR_OPTIMIZED2: + case CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE_NOCTOR_OPTIMIZED2_NOJITOPT: + helperArg = (void*)(size_t)m_compHnd->getThreadLocalFieldInfo(pResolvedToken->hField, false); + break; + case CORINFO_HELP_GETDYNAMIC_GCTHREADSTATIC_BASE_NOCTOR_OPTIMIZED: + helperArg = (void*)(size_t)m_compHnd->getThreadLocalFieldInfo(pResolvedToken->hField, true); + break; + case CORINFO_HELP_GETDYNAMIC_GCTHREADSTATIC_BASE_NOCTOR: + case CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE_NOCTOR: + case CORINFO_HELP_GETDYNAMIC_GCTHREADSTATIC_BASE: + case CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE: + helperArg = (void*)m_compHnd->getClassThreadStaticDynamicInfo(pResolvedToken->hClass); + break; + default: + // TODO + assert(0); + break; + } + void *helperFtnSlot; + void *helperFtn = m_compHnd->getHelperFtn(pFieldInfo->helper, &helperFtnSlot); + // Call helper to obtain thread static base address + AddIns(INTOP_CALL_HELPER_PP); + m_pLastIns->data[0] = GetDataItemIndex(helperFtn); + m_pLastIns->data[1] = GetDataItemIndex(helperFtnSlot); + m_pLastIns->data[2] = GetDataItemIndex(helperArg); + PushInterpType(InterpTypeByRef, NULL); + m_pLastIns->SetDVar(m_pStackPointer[-1].var); + + // Add field offset + m_pStackPointer--; + AddIns(INTOP_ADD_P_IMM); + m_pLastIns->data[0] = (int32_t)pFieldInfo->offset; + m_pLastIns->SetSVar(m_pStackPointer[0].var); + PushInterpType(InterpTypeByRef, NULL); + m_pLastIns->SetDVar(m_pStackPointer[-1].var); + break; + } + default: + // TODO + assert(0); + break; + } +} + +void InterpCompiler::EmitStaticFieldAccess(InterpType interpFieldType, CORINFO_FIELD_INFO *pFieldInfo, CORINFO_RESOLVED_TOKEN *pResolvedToken, bool isLoad) +{ + EmitStaticFieldAddress(pFieldInfo, pResolvedToken); + if (isLoad) + EmitLdind(interpFieldType, pFieldInfo->structType, 0); + else + EmitStind(interpFieldType, pFieldInfo->structType, 0, true); +} + +void InterpCompiler::EmitLdLocA(int32_t var) +{ + AddIns(INTOP_LDLOCA); + m_pLastIns->SetSVar(var); + m_pVars[var].indirects++; + PushInterpType(InterpTypeByRef, NULL); + m_pLastIns->SetDVar(m_pStackPointer[-1].var); +} + int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) { bool readonly = false; bool tailcall = false; + bool volatile_ = false; CORINFO_CLASS_HANDLE constrainedClass = NULL; uint8_t *codeEnd; int numArgs = m_methodInfo->args.hasThis() + m_methodInfo->args.numArgs; @@ -1466,6 +1702,13 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) goto exit_bad_code; } + if ((methodInfo->options & CORINFO_OPT_INIT_LOCALS) && m_ILLocalsSize > 0) + { + AddIns(INTOP_INITLOCALS); + m_pLastIns->data[0] = m_ILLocalsOffset; + m_pLastIns->data[1] = m_ILLocalsSize; + } + codeEnd = m_ip + m_ILCodeSize; linkBBlocks = true; @@ -1646,6 +1889,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitLoadVar(*m_ip - CEE_LDARG_0); m_ip++; break; + case CEE_LDARGA_S: + EmitLdLocA(m_ip[1]); + m_ip += 2; + break; case CEE_STARG_S: EmitStoreVar(m_ip[1]); m_ip += 2; @@ -1661,6 +1908,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitLoadVar(numArgs + *m_ip - CEE_LDLOC_0); m_ip++; break; + case CEE_LDLOCA_S: + EmitLdLocA(numArgs + m_ip[1]); + m_ip += 2; + break; case CEE_STLOC_S: EmitStoreVar(numArgs + m_ip[1]); m_ip += 2; @@ -2216,7 +2467,368 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) readonly = false; tailcall = false; break; + case CEE_NEWOBJ: + { + CORINFO_METHOD_HANDLE ctorMethod; + CORINFO_SIG_INFO ctorSignature; + CORINFO_CLASS_HANDLE ctorClass; + m_ip++; + ctorMethod = ResolveMethodToken(getU4LittleEndian(m_ip)); + m_ip += 4; + + m_compHnd->getMethodSig(ctorMethod, &ctorSignature); + ctorClass = m_compHnd->getMethodClass(ctorMethod); + int32_t numArgs = ctorSignature.numArgs; + + // TODO Special case array ctor / string ctor + m_pStackPointer -= numArgs; + + // Allocate callArgs for the call, this + numArgs + terminator + int32_t *callArgs = (int32_t*) AllocMemPool((numArgs + 2) * sizeof(int32_t)); + for (int i = 0; i < numArgs; i++) + callArgs[i + 1] = m_pStackPointer[i].var; + callArgs[numArgs + 1] = -1; + + // Push the return value and `this` argument to the ctor + InterpType retType = GetInterpType(m_compHnd->asCorInfoType(ctorClass)); + int32_t vtsize = 0; + if (retType == InterpTypeVT) + { + vtsize = m_compHnd->getClassSize(ctorClass); + PushTypeVT(ctorClass, vtsize); + PushInterpType(InterpTypeByRef, NULL); + } + else + { + PushInterpType(retType, ctorClass); + PushInterpType(retType, ctorClass); + } + int32_t dVar = m_pStackPointer[-2].var; + int32_t thisVar = m_pStackPointer[-1].var; + // Consider this arg as being defined, although newobj defines it + AddIns(INTOP_DEF); + m_pLastIns->SetDVar(thisVar); + callArgs[0] = thisVar; + + if (retType == InterpTypeVT) + { + AddIns(INTOP_NEWOBJ_VT); + m_pLastIns->data[1] = (int32_t)ALIGN_UP_TO(vtsize, INTERP_STACK_SLOT_SIZE); + } + else + { + AddIns(INTOP_NEWOBJ); + m_pLastIns->data[1] = GetDataItemIndex(ctorClass); + } + m_pLastIns->data[0] = GetMethodDataItemIndex(ctorMethod); + m_pLastIns->SetSVar(CALL_ARGS_SVAR); + m_pLastIns->SetDVar(dVar); + + m_pLastIns->flags |= INTERP_INST_FLAG_CALL; + m_pLastIns->info.pCallInfo = (InterpCallInfo*)AllocMemPool0(sizeof(InterpCallInfo)); + m_pLastIns->info.pCallInfo->pCallArgs = callArgs; + + // Pop this, the result of the newobj still remains on the stack + m_pStackPointer--; + break; + } + case CEE_POP: + CHECK_STACK(1); + AddIns(INTOP_NOP); + m_pStackPointer--; + m_ip++; + break; + case CEE_LDFLDA: + { + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_ADDRESS, &fieldInfo); + + bool isStatic = !!(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC); + + if (isStatic) + { + // Pop unused object reference + m_pStackPointer--; + EmitStaticFieldAddress(&fieldInfo, &resolvedToken); + } + else + { + assert(fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE); + m_pStackPointer--; + AddIns(INTOP_LDFLDA); + m_pLastIns->data[0] = (int32_t)fieldInfo.offset; + m_pLastIns->SetSVar(m_pStackPointer[0].var); + PushInterpType(InterpTypeByRef, NULL); + m_pLastIns->SetDVar(m_pStackPointer[-1].var); + } + + m_ip += 5; + break; + } + case CEE_LDFLD: + { + CHECK_STACK(1); + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_GET, &fieldInfo); + + CorInfoType fieldType = fieldInfo.fieldType; + bool isStatic = !!(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC); + InterpType interpFieldType = GetInterpType(fieldType); + + if (isStatic) + { + // Pop unused object reference + m_pStackPointer--; + EmitStaticFieldAccess(interpFieldType, &fieldInfo, &resolvedToken, true); + } + else + { + assert(fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE); + m_pStackPointer--; + int sizeDataIndexOffset = 0; + if (m_pStackPointer[0].type == StackTypeVT) + { + sizeDataIndexOffset = 1; + AddIns(INTOP_MOV_SRC_OFF); + m_pLastIns->data[1] = interpFieldType; + } + else + { + int32_t opcode = GetLdindForType(interpFieldType); + AddIns(opcode); + } + m_pLastIns->SetSVar(m_pStackPointer[0].var); + m_pLastIns->data[0] = (int32_t)fieldInfo.offset; + if (interpFieldType == InterpTypeVT) + { + CORINFO_CLASS_HANDLE fieldClass = fieldInfo.structType; + int size = m_compHnd->getClassSize(fieldClass); + m_pLastIns->data[1 + sizeDataIndexOffset] = size; + PushTypeVT(fieldClass, size); + } + else + { + PushInterpType(interpFieldType, NULL); + } + m_pLastIns->SetDVar(m_pStackPointer[-1].var); + } + + m_ip += 5; + if (volatile_) + { + // Acquire membar + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + break; + } + case CEE_STFLD: + { + CHECK_STACK(2); + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_GET, &fieldInfo); + + CorInfoType fieldType = fieldInfo.fieldType; + bool isStatic = !!(fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC); + InterpType interpFieldType = GetInterpType(fieldType); + + if (volatile_) + { + // Release memory barrier + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + + if (isStatic) + { + EmitStaticFieldAccess(interpFieldType, &fieldInfo, &resolvedToken, false); + // Pop the unused object reference + m_pStackPointer--; + } + else + { + assert(fieldInfo.fieldAccessor == CORINFO_FIELD_INSTANCE); + EmitStind(interpFieldType, fieldInfo.structType, fieldInfo.offset, false); + } + m_ip += 5; + + break; + } + case CEE_LDSFLDA: + { + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_GET, &fieldInfo); + + EmitStaticFieldAddress(&fieldInfo, &resolvedToken); + + m_ip += 5; + break; + } + case CEE_LDSFLD: + { + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_GET, &fieldInfo); + + CorInfoType fieldType = fieldInfo.fieldType; + InterpType interpFieldType = GetInterpType(fieldType); + + EmitStaticFieldAccess(interpFieldType, &fieldInfo, &resolvedToken, true); + + if (volatile_) + { + // Acquire memory barrier + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + m_ip += 5; + break; + } + case CEE_STSFLD: + { + CHECK_STACK(1); + CORINFO_RESOLVED_TOKEN resolvedToken; + CORINFO_FIELD_INFO fieldInfo; + uint32_t token = getU4LittleEndian(m_ip + 1); + ResolveToken(token, CORINFO_TOKENKIND_Field, &resolvedToken); + m_compHnd->getFieldInfo(&resolvedToken, m_methodHnd, CORINFO_ACCESS_GET, &fieldInfo); + + CorInfoType fieldType = fieldInfo.fieldType; + InterpType interpFieldType = GetInterpType(fieldType); + + if (volatile_) + { + // Release memory barrier + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + EmitStaticFieldAccess(interpFieldType, &fieldInfo, &resolvedToken, false); + m_ip += 5; + break; + } + case CEE_LDIND_I1: + case CEE_LDIND_U1: + case CEE_LDIND_I2: + case CEE_LDIND_U2: + case CEE_LDIND_I4: + case CEE_LDIND_U4: + case CEE_LDIND_I8: + case CEE_LDIND_I: + case CEE_LDIND_R4: + case CEE_LDIND_R8: + case CEE_LDIND_REF: + { + InterpType interpType = InterpTypeVoid; + switch(opcode) + { + case CEE_LDIND_I1: + interpType = InterpTypeI1; + break; + case CEE_LDIND_U1: + interpType = InterpTypeU1; + break; + case CEE_LDIND_I2: + interpType = InterpTypeI2; + break; + case CEE_LDIND_U2: + interpType = InterpTypeU2; + break; + case CEE_LDIND_I4: + case CEE_LDIND_U4: + interpType = InterpTypeI4; + break; + case CEE_LDIND_I8: + interpType = InterpTypeI8; + break; + case CEE_LDIND_I: + interpType = InterpTypeI; + break; + case CEE_LDIND_R4: + interpType = InterpTypeR4; + break; + case CEE_LDIND_R8: + interpType = InterpTypeR8; + break; + case CEE_LDIND_REF: + interpType = InterpTypeO; + break; + default: + assert(0); + } + EmitLdind(interpType, NULL, 0); + if (volatile_) + { + // Acquire memory barrier + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + m_ip++; + break; + } + case CEE_STIND_I1: + case CEE_STIND_I2: + case CEE_STIND_I4: + case CEE_STIND_I8: + case CEE_STIND_I: + case CEE_STIND_R4: + case CEE_STIND_R8: + case CEE_STIND_REF: + { + InterpType interpType = InterpTypeVoid; + switch(opcode) + { + case CEE_STIND_I1: + interpType = InterpTypeI1; + break; + case CEE_STIND_I2: + interpType = InterpTypeI2; + break; + case CEE_STIND_I4: + interpType = InterpTypeI4; + break; + case CEE_STIND_I8: + interpType = InterpTypeI8; + break; + case CEE_STIND_I: + interpType = InterpTypeI; + break; + case CEE_STIND_R4: + interpType = InterpTypeR4; + break; + case CEE_STIND_R8: + interpType = InterpTypeR8; + break; + case CEE_STIND_REF: + interpType = InterpTypeO; + break; + default: + assert(0); + } + if (volatile_) + { + // Release memory barrier + AddIns(INTOP_MEMBAR); + volatile_ = false; + } + EmitStind(interpType, NULL, 0, false); + m_ip++; + break; + } case CEE_PREFIX1: m_ip++; switch (*m_ip + 256) @@ -2225,6 +2837,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitLoadVar(getU2LittleEndian(m_ip + 1)); m_ip += 3; break; + case CEE_LDARGA: + EmitLdLocA(getU2LittleEndian(m_ip + 1)); + m_ip += 3; + break; case CEE_STARG: EmitStoreVar(getU2LittleEndian(m_ip + 1)); m_ip += 3; @@ -2233,6 +2849,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitLoadVar(numArgs + getU2LittleEndian(m_ip + 1)); m_ip += 3; break; + case CEE_LDLOCA: + EmitLdLocA(numArgs + getU2LittleEndian(m_ip + 1)); + m_ip += 3; + break; case CEE_STLOC: EmitStoreVar(numArgs + getU2LittleEndian(m_ip + 1));\ m_ip += 3; @@ -2279,6 +2899,10 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) tailcall = true; m_ip++; break; + case CEE_VOLATILE: + volatile_ = true; + m_ip++; + break; default: assert(0); break; @@ -2424,6 +3048,12 @@ void InterpCompiler::PrintInsData(InterpInst *ins, int32_t insOffset, const int3 case InterpOpInt: printf(" %d", *pData); break; + case InterpOpTwoInts: + printf(" %d,%d", *pData, *(pData + 1)); + break; + case InterpOpThreeInts: + printf(" %d,%d,%d", *pData, *(pData + 1), *(pData + 2)); + break; case InterpOpBranch: if (ins) printf(" BB%d", ins->info.pTargetBB->index); diff --git a/src/coreclr/interpreter/compiler.h b/src/coreclr/interpreter/compiler.h index 1f93a9a308f255..a1d066b90f558e 100644 --- a/src/coreclr/interpreter/compiler.h +++ b/src/coreclr/interpreter/compiler.h @@ -285,6 +285,9 @@ class InterpCompiler int GenerateCode(CORINFO_METHOD_INFO* methodInfo); + void ResolveToken(uint32_t token, CorInfoTokenKind tokenKind, CORINFO_RESOLVED_TOKEN *pResolvedToken); + CORINFO_METHOD_HANDLE ResolveMethodToken(uint32_t token); + void* AllocMethodData(size_t numBytes); // FIXME Mempool allocation currently leaks. We need to add an allocator and then // free all memory when method is finished compilling. @@ -369,6 +372,11 @@ class InterpCompiler void EmitCompareOp(int32_t opBase); void EmitCall(CORINFO_CLASS_HANDLE constrainedClass, bool readonly, bool tailcall); bool EmitCallIntrinsics(CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig); + void EmitLdind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset); + void EmitStind(InterpType type, CORINFO_CLASS_HANDLE clsHnd, int32_t offset, bool reverseSVarOrder); + void EmitStaticFieldAddress(CORINFO_FIELD_INFO *pFieldInfo, CORINFO_RESOLVED_TOKEN *pResolvedToken); + void EmitStaticFieldAccess(InterpType interpFieldType, CORINFO_FIELD_INFO *pFieldInfo, CORINFO_RESOLVED_TOKEN *pResolvedToken, bool isLoad); + void EmitLdLocA(int32_t var); // Var Offset allocator TArray *m_pActiveCalls; diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index fcdf33aa11575c..dccbee84e60699 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -6,15 +6,19 @@ // are stored in uint32_t slots in the instruction stream for simplicity. In the future // we should add compact opcodes where all data is in uint16_t slots. -OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_RET, "ret", 2, 0, 1, InterpOpNoArgs) OPDEF(INTOP_RET_VT, "ret.vt", 3, 0, 1, InterpOpInt) OPDEF(INTOP_RET_VOID, "ret.void", 1, 0, 0, InterpOpNoArgs) +OPDEF(INTOP_INITLOCALS, "initlocals", 3, 0, 0, InterpOpTwoInts) +OPDEF(INTOP_MEMBAR, "membar", 1, 0, 0, InterpOpNoArgs) + OPDEF(INTOP_LDC_I4, "ldc.i4", 3, 1, 0, InterpOpInt) OPDEF(INTOP_LDC_I4_0, "ldc.i4.0", 2, 1, 0, InterpOpNoArgs) OPDEF(INTOP_LDC_I8_0, "ldc.i8.0", 2, 1, 0, InterpOpNoArgs) +OPDEF(INTOP_LDPTR, "ldptr", 3, 1, 0, InterpOpInt) + OPDEF(INTOP_MOV_I4_I1, "mov.i4.i1", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_MOV_I4_U1, "mov.i4.u1", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_MOV_I4_I2, "mov.i4.i2", 3, 1, 1, InterpOpNoArgs) @@ -132,6 +136,9 @@ OPDEF(INTOP_CONV_U8_R4, "conv.u8.r4", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_CONV_U8_R8, "conv.u8.r8", 3, 1, 1, InterpOpNoArgs) // Unary operations end +OPDEF(INTOP_ADD_I4_IMM, "add.i4.imm", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_ADD_I8_IMM, "add.i8.imm", 4, 1, 1, InterpOpInt) + // Binary operations OPDEF(INTOP_ADD_I4, "add.i4", 4, 1, 2, InterpOpNoArgs) @@ -191,7 +198,42 @@ OPDEF(INTOP_CLT_UN_R4, "clt.un.r4", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_CLT_UN_R8, "clt.un.r8", 4, 1, 2, InterpOpNoArgs) // Binary operations end +// Fields +OPDEF(INTOP_LDIND_I1, "ldind.i1", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_U1, "ldind.u1", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_I2, "ldind.i2", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_U2, "ldind.u2", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_I4, "ldind.i4", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_I8, "ldind.i8", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_R4, "ldind.r4", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_R8, "ldind.r8", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_O, "ldind.o", 4, 1, 1, InterpOpInt) +OPDEF(INTOP_LDIND_VT, "ldind.vt", 5, 1, 1, InterpOpTwoInts) + +OPDEF(INTOP_STIND_I1, "stind.i1", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_U1, "stind.u1", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_I2, "stind.i2", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_U2, "stind.u2", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_I4, "stind.i4", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_I8, "stind.i8", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_R4, "stind.r4", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_R8, "stind.r8", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_O, "stind.o", 4, 0, 2, InterpOpInt) +OPDEF(INTOP_STIND_VT, "stind.vt", 5, 0, 2, InterpOpTwoInts) +OPDEF(INTOP_STIND_VT_NOREF, "stind.vt.noref", 5, 0, 2, InterpOpTwoInts) + +OPDEF(INTOP_LDFLDA, "ldflda", 4, 1, 1, InterpOpInt) + // Calls OPDEF(INTOP_CALL, "call", 4, 1, 1, InterpOpMethodToken) +OPDEF(INTOP_NEWOBJ, "newobj", 5, 1, 1, InterpOpMethodToken) +OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodToken) + +OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts) OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs) + +// All instructions after this point are IROPS, instructions that are not emitted/executed +OPDEF(INTOP_NOP, "nop", 1, 0, 0, InterpOpNoArgs) +OPDEF(INTOP_DEF, "def", 2, 1, 0, InterpOpNoArgs) +OPDEF(INTOP_MOV_SRC_OFF, "mov.src.off", 6, 1, 1, InterpOpThreeInts) diff --git a/src/coreclr/interpreter/intops.h b/src/coreclr/interpreter/intops.h index fa64b28a971974..523e8665d49bb3 100644 --- a/src/coreclr/interpreter/intops.h +++ b/src/coreclr/interpreter/intops.h @@ -13,6 +13,8 @@ typedef enum { InterpOpNoArgs, InterpOpInt, + InterpOpTwoInts, + InterpOpThreeInts, InterpOpBranch, InterpOpSwitch, InterpOpMethodToken, @@ -39,11 +41,22 @@ int CEEOpcodeSize(const uint8_t *ip, const uint8_t *codeEnd); #ifdef TARGET_64BIT #define INTOP_MOV_P INTOP_MOV_8 #define INTOP_LDNULL INTOP_LDC_I8_0 +#define INTOP_LDIND_I INTOP_LDIND_I8 +#define INTOP_STIND_I INTOP_STIND_I8 +#define INTOP_ADD_P_IMM INTOP_ADD_I8_IMM #else #define INTOP_MOV_P INTOP_MOV_4 #define INTOP_LDNULL INTOP_LDC_I4_0 +#define INTOP_LDIND_I INTOP_LDIND_I4 +#define INTOP_STIND_I INTOP_STIND_I4 +#define INTOP_ADD_P_IMM INTOP_ADD_I4_IMM #endif +static inline bool InterpOpIsEmitNop(int32_t opcode) +{ + return opcode >= INTOP_NOP && opcode != INTOP_MOV_SRC_OFF; +} + static inline bool InterpOpIsUncondBranch(int32_t opcode) { return opcode == INTOP_BR; diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index ced6cfb7a7c441..b7d0dc3d02da3b 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -7,6 +7,8 @@ #include "interpexec.h" +typedef void* (*HELPER_FTN_PP)(void*); + thread_local InterpThreadContext *t_pThreadContext = NULL; InterpThreadContext* InterpGetThreadContext() @@ -31,6 +33,8 @@ InterpThreadContext* InterpGetThreadContext() #define LOCAL_VAR_ADDR(offset,type) ((type*)(stack + (offset))) #define LOCAL_VAR(offset,type) (*LOCAL_VAR_ADDR(offset, type)) +// TODO once we have basic EH support +#define NULL_CHECK(o) void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext) { @@ -42,7 +46,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr ip = pFrame->startIp + sizeof(InterpMethod*) / sizeof(int32_t); stack = pFrame->pStack; - int32_t returnOffset, callArgsOffset; + int32_t returnOffset, callArgsOffset, methodSlot; MAIN_LOOP: while (true) @@ -56,6 +60,14 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr switch (*ip) { + case INTOP_INITLOCALS: + memset(stack + ip[1], 0, ip[2]); + ip += 3; + break; + case INTOP_MEMBAR: + MemoryBarrier(); + ip++; + break; case INTOP_LDC_I4: LOCAL_VAR(ip[1], int32_t) = ip[2]; ip += 3; @@ -68,6 +80,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr LOCAL_VAR(ip[1], int64_t) = 0; ip += 2; break; + case INTOP_LDPTR: + LOCAL_VAR(ip[1], void*) = pMethod->pDataItems[ip[2]]; + ip += 3; + break; case INTOP_RET: // Return stack slot sized value *(int64_t*)pFrame->pRetVal = LOCAL_VAR(ip[1], int64_t); @@ -78,6 +94,11 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr case INTOP_RET_VOID: goto EXIT_FRAME; + case INTOP_LDLOCA: + LOCAL_VAR(ip[1], void*) = stack + ip[2]; + ip += 3; + break;; + #define MOV(argtype1,argtype2) \ LOCAL_VAR(ip [1], argtype1) = LOCAL_VAR(ip [2], argtype2); \ ip += 3; @@ -496,7 +517,14 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr LOCAL_VAR(ip[1], double) = LOCAL_VAR(ip[2], double) + LOCAL_VAR(ip[3], double); ip += 4; break; - + case INTOP_ADD_I4_IMM: + LOCAL_VAR(ip[1], int32_t) = LOCAL_VAR(ip[2], int32_t) + ip[3]; + ip += 4; + break; + case INTOP_ADD_I8_IMM: + LOCAL_VAR(ip[1], int64_t) = LOCAL_VAR(ip[2], int64_t) + ip[3]; + ip += 4; + break; case INTOP_SUB_I4: LOCAL_VAR(ip[1], int32_t) = LOCAL_VAR(ip[2], int32_t) - LOCAL_VAR(ip[3], int32_t); ip += 4; @@ -692,13 +720,146 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr CMP_BINOP_FP(double, <, 1); break; +#define LDIND(dtype, ftype) \ + do { \ + char *src = LOCAL_VAR(ip[2], char*); \ + NULL_CHECK(src); \ + LOCAL_VAR(ip[1], dtype) = *(ftype*)(src + ip[3]); \ + ip += 4; \ + } while (0) + + case INTOP_LDIND_I1: + LDIND(int32_t, int8_t); + break; + case INTOP_LDIND_U1: + LDIND(int32_t, uint8_t); + break; + case INTOP_LDIND_I2: + LDIND(int32_t, int16_t); + break; + case INTOP_LDIND_U2: + LDIND(int32_t, uint16_t); + break; + case INTOP_LDIND_I4: + LDIND(int32_t, int32_t); + break; + case INTOP_LDIND_I8: + LDIND(int64_t, int64_t); + break; + case INTOP_LDIND_R4: + LDIND(float, float); + break; + case INTOP_LDIND_R8: + LDIND(double, double); + break; + case INTOP_LDIND_O: + LDIND(OBJECTREF, OBJECTREF); + break; + + case INTOP_LDIND_VT: + { + char *src = LOCAL_VAR(ip[2], char*); + NULL_CHECK(obj); + memcpy(stack + ip[1], (char*)src + ip[3], ip[4]); + ip += 5; + break; + } + +#define STIND(dtype, ftype) \ + do \ + { \ + char *dst = LOCAL_VAR(ip[1], char*); \ + NULL_CHECK(dst); \ + *(ftype*)(dst + ip[3]) = (ftype)(LOCAL_VAR(ip[2], dtype)); \ + ip += 4; \ + } while (0) + + case INTOP_STIND_I1: + STIND(int32_t, int8_t); + break; + case INTOP_STIND_U1: + STIND(int32_t, uint8_t); + break; + case INTOP_STIND_I2: + STIND(int32_t, int16_t); + break; + case INTOP_STIND_U2: + STIND(int32_t, uint16_t); + break; + case INTOP_STIND_I4: + STIND(int32_t, int32_t); + break; + case INTOP_STIND_I8: + STIND(int64_t, int64_t); + break; + case INTOP_STIND_R4: + STIND(float, float); + break; + case INTOP_STIND_R8: + STIND(double, double); + break; + case INTOP_STIND_O: + { + char *dst = LOCAL_VAR(ip[1], char*); + OBJECTREF storeObj = LOCAL_VAR(ip[2], OBJECTREF); + NULL_CHECK(obj); + SetObjectReferenceUnchecked((OBJECTREF*)(dst + ip[3]), storeObj); + ip += 4; + break; + } + case INTOP_STIND_VT_NOREF: + { + char *dest = LOCAL_VAR(ip[1], char*); + NULL_CHECK(dest); + memcpyNoGCRefs(dest + ip[3], stack + ip[2], ip[4]); + ip += 5; + break; + } + case INTOP_STIND_VT: + { + MethodTable *pMT = (MethodTable*)pMethod->pDataItems[ip[4]]; + char *dest = LOCAL_VAR(ip[1], char*); + NULL_CHECK(dest); + CopyValueClassUnchecked(dest + ip[3], stack + ip[2], pMT); + ip += 5; + break; + } + case INTOP_LDFLDA: + { + char *src = LOCAL_VAR(ip[2], char*); + NULL_CHECK(src); + LOCAL_VAR(ip[1], char*) = src + ip[3]; + ip += 4; + break; + } + + case INTOP_CALL_HELPER_PP: + { + HELPER_FTN_PP helperFtn = (HELPER_FTN_PP)pMethod->pDataItems[ip[2]]; + HELPER_FTN_PP* helperFtnSlot = (HELPER_FTN_PP*)pMethod->pDataItems[ip[3]]; + void* helperArg = pMethod->pDataItems[ip[4]]; + + if (!helperFtn) + helperFtn = *helperFtnSlot; + // This can call either native or compiled managed code. For an interpreter + // only configuration, this might be problematic, at least performance wise. + // FIXME We will need to handle exception throwing here. + LOCAL_VAR(ip[1], void*) = helperFtn(helperArg); + + ip += 5; + break; + } + case INTOP_CALL: { - size_t targetMethod = (size_t)pMethod->pDataItems[ip[3]]; returnOffset = ip[1]; callArgsOffset = ip[2]; - const int32_t *targetIp; + methodSlot = ip[3]; + ip += 4; +CALL_INTERP_SLOT: + const int32_t *targetIp; + size_t targetMethod = (size_t)pMethod->pDataItems[methodSlot]; if (targetMethod & INTERP_METHOD_DESC_TAG) { // First execution of this call. Ensure target method is compiled and @@ -718,7 +879,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr pMD->PrepareInitialCode(CallerGCMode::Coop); code = pMD->GetNativeCode(); } - pMethod->pDataItems[ip[3]] = (void*)code; + pMethod->pDataItems[methodSlot] = (void*)code; targetIp = (const int32_t*)code; } else @@ -729,8 +890,8 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr targetIp = (const int32_t*)targetMethod; } - // Save current execution state once we return from called method - pFrame->ip = ip + 4; + // Save current execution state for when we return from called method + pFrame->ip = ip; // Allocate child frame. { @@ -753,6 +914,39 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr pThreadContext->pStackPointer = stack + pMethod->allocaSize; break; } + case INTOP_NEWOBJ: + { + returnOffset = ip[1]; + callArgsOffset = ip[2]; + methodSlot = ip[3]; + + OBJECTREF objRef = AllocateObject((MethodTable*)pMethod->pDataItems[ip[4]]); + + // This is return value + LOCAL_VAR(returnOffset, OBJECTREF) = objRef; + // Set `this` arg for ctor call + LOCAL_VAR (callArgsOffset, OBJECTREF) = objRef; + ip += 5; + + goto CALL_INTERP_SLOT; + } + case INTOP_NEWOBJ_VT: + { + returnOffset = ip[1]; + callArgsOffset = ip[2]; + methodSlot = ip[3]; + + int32_t vtSize = ip[4]; + void *vtThis = stack + returnOffset; + + // clear the valuetype + memset(vtThis, 0, vtSize); + // pass the address of the valuetype + LOCAL_VAR(callArgsOffset, void*) = vtThis; + + ip += 5; + goto CALL_INTERP_SLOT; + } case INTOP_FAILFAST: assert(0); break; diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index d26415da5a4d12..bd5b27581a52b0 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -4,10 +4,46 @@ using System; using System.Runtime.CompilerServices; +public struct MyStruct +{ + public int a; + + public MyStruct(int val) + { + a = val; + } +} + +public class MyObj +{ + public int ct; + public MyStruct str; + + public MyObj(int val) + { + str = new MyStruct(val); + ct = 10; + } +} + +public struct MyStruct2 +{ + public int ct; + public MyStruct str; + + public MyStruct2(int val) + { + str = new MyStruct(val); + ct = 20; + } +} + public class InterpreterTest { static int Main(string[] args) { + jitField1 = 42; + jitField2 = 43; RunInterpreterTests(); return 100; } @@ -25,6 +61,14 @@ public static void RunInterpreterTests() if (!PowLoop(20, 10, 1661992960)) Environment.FailFast(null); + if (!TestJitFields()) + Environment.FailFast(null); + // Disable below tests because they are potentially unstable since they do allocation + // and we currently don't have GC support. They should pass locally though. +// if (!TestFields()) +// Environment.FailFast(null); +// if (!TestSpecialFields()) +// Environment.FailFast(null); } public static int Mul4(int a, int b, int c, int d) @@ -72,4 +116,74 @@ public static bool PowLoop(int n, long nr, int expected) ret *= nr; return (int)ret == expected; } + + public static int jitField1; + [ThreadStatic] + public static int jitField2; + + public static bool TestJitFields() + { + // These fields are initialized by the JIT + // Test that interpreter accesses the correct address + if (jitField1 != 42) + return false; + if (jitField2 != 43) + return false; + return true; + } + + public static MyObj staticObj; + public static MyStruct2 staticStr; + + public static void WriteInt(ref int a, int ct) + { + a = ct; + } + + public static int ReadInt(ref int a) + { + return a; + } + + public static bool TestFields() + { + MyObj obj = new MyObj(1); + MyStruct2 str = new MyStruct2(2); + + int sum = obj.str.a + str.str.a + obj.ct + str.ct; + if (sum != 33) + return false; + + staticObj = obj; + staticStr = str; + + sum = staticObj.str.a + staticStr.str.a + staticObj.ct + staticStr.ct; + if (sum != 33) + return false; + + WriteInt(ref str.str.a, 11); + WriteInt(ref staticObj.str.a, 22); + sum = ReadInt(ref str.str.a) + ReadInt(ref staticObj.str.a); + if (sum != 33) + return false; + + return true; + } + + [ThreadStatic] + public static MyObj threadStaticObj; + [ThreadStatic] + public static MyStruct2 threadStaticStr; + + public static bool TestSpecialFields() + { + threadStaticObj = new MyObj(1); + threadStaticStr = new MyStruct2(2); + + int sum = threadStaticObj.str.a + threadStaticStr.str.a + threadStaticObj.ct + threadStaticStr.ct; + if (sum != 33) + return false; + + return true; + } }