All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.adoptopenjdk.jitwatch.model.bytecode.BytecodeAnnotationBuilder Maven / Gradle / Ivy

Go to download

A Maven plugin that scans the project artifact and its dependencies for methods that cannot be inlined by the JIT compiler. It uses the JarScan utility from the JitWatch project to do that. See https://github.com/AdoptOpenJDK/jitwatch .

There is a newer version: 1.1
Show newest version
/*
 * Copyright (c) 2013, 2014 Chris Newland.
 * Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
 * Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
 */
package org.adoptopenjdk.jitwatch.model.bytecode;

import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_COUNT;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_NOT_TAKEN;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_PROB;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BRANCH_TAKEN;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_CODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_ID;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_METHOD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_NAME;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_REASON;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_BCI;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.ATTR_TYPE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.C_SPACE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.DEBUG_LOGGING_BYTECODE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_PARSE_HIR;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_BC;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_BRANCH;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_CALL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_DIRECT_CALL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_ELIMINATE_ALLOCATION;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_ELIMINATE_LOCK;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INLINE_FAIL;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INLINE_SUCCESS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_INTRINSIC;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_JVMS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_KLASS;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_METHOD;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PARSE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PARSE_DONE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PHASE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_TYPE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_UNCOMMON_TRAP;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_DEPENDENCY;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PHASE_DONE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.TAG_PREDICTED_CALL;


import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.adoptopenjdk.jitwatch.journal.AbstractJournalVisitable;
import org.adoptopenjdk.jitwatch.journal.JournalUtil;
import org.adoptopenjdk.jitwatch.model.AnnotationException;
import org.adoptopenjdk.jitwatch.model.CompilerName;
import org.adoptopenjdk.jitwatch.model.IMetaMember;
import org.adoptopenjdk.jitwatch.model.IParseDictionary;
import org.adoptopenjdk.jitwatch.model.IReadOnlyJITDataModel;
import org.adoptopenjdk.jitwatch.model.LogParseException;
import org.adoptopenjdk.jitwatch.model.Tag;
import org.adoptopenjdk.jitwatch.util.ParseUtil;
import org.adoptopenjdk.jitwatch.util.StringUtil;
import org.adoptopenjdk.jitwatch.util.TooltipUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BytecodeAnnotationBuilder extends AbstractJournalVisitable
{
	private static final Logger logger = LoggerFactory.getLogger(BytecodeAnnotationBuilder.class);

	private IMetaMember member;

	private IReadOnlyJITDataModel model;

	private BytecodeAnnotations bcAnnotations = new BytecodeAnnotations();

	public BytecodeAnnotationBuilder()
	{
		ignoreTags.add(TAG_KLASS);
		ignoreTags.add(TAG_TYPE);
		ignoreTags.add(TAG_DEPENDENCY);	
		ignoreTags.add(TAG_PHASE);
		ignoreTags.add(TAG_PARSE_DONE);
		ignoreTags.add(TAG_DIRECT_CALL);
		ignoreTags.add(TAG_PARSE);
		ignoreTags.add(TAG_PHASE_DONE);
		ignoreTags.add(TAG_PREDICTED_CALL);
	}
	
	public BytecodeAnnotations buildBytecodeAnnotations(final IMetaMember member, IReadOnlyJITDataModel model)
			throws AnnotationException
	{
		this.member = member;
		this.model = model;

		bcAnnotations.clear();

		if (member != null)
		{
			if (!member.isCompiled())
			{
				return bcAnnotations;
			}

			try
			{
				JournalUtil.visitParseTagsOfLastTask(member.getJournal(), this);

				JournalUtil.visitOptimizerTagsOfLastTask(member.getJournal(), this);
			}
			catch (LogParseException e)
			{
				logger.error("Error building bytecode annotations", e);

				Throwable cause = e.getCause();

				if (cause != null)
				{
					logger.error("Cause", cause);

					if (cause instanceof AnnotationException)
					{
						throw (AnnotationException) cause;
					}
				}
			}
		}
		return bcAnnotations;
	}

	@Override
	public void visitTag(Tag tag, IParseDictionary parseDictionary) throws LogParseException
	{
		switch (tag.getName())
		{
		case TAG_PARSE:
			visitTagParse(tag, parseDictionary);
			break;

		case TAG_ELIMINATE_ALLOCATION:
			visitTagEliminateAllocation(tag, parseDictionary);
			break;

		case TAG_ELIMINATE_LOCK:
			visitTagEliminateLock(tag, parseDictionary);
			break;

		default:
			handleOther(tag);
			break;
		}
	}

	private void visitTagParse(Tag tag, IParseDictionary parseDictionary) throws LogParseException
	{
		String methodID = tag.getAttribute(ATTR_METHOD);

		if (JournalUtil.memberMatchesMethodID(member, methodID, parseDictionary))
		{
			try
			{
				final CompilerName compilerName = JournalUtil.getCompilerNameForLastTask(member.getJournal());

				buildParseTagAnnotations(tag, bcAnnotations, compilerName, parseDictionary);
			}
			catch (Exception e)
			{
				throw new LogParseException("Could not parse annotations", e);
			}
		}
		else
		{
			logger.warn("Parse tag does not appear to be for member {}", member.getFullyQualifiedMemberName());
		}
	}

	private void visitTagEliminateAllocation(Tag tag, IParseDictionary parseDictionary)
	{
		List childrenJVMS = tag.getNamedChildren(TAG_JVMS);

		for (Tag tagJVMS : childrenJVMS)
		{
			String bci = tagJVMS.getAttribute(ATTR_BCI);

			if (bci != null)
			{
				try
				{
					int bciValue = Integer.parseInt(bci);

					BytecodeInstruction instr = getInstructionAtIndex(bciValue);

					if (instr != null)
					{
						StringBuilder builder = new StringBuilder();
						builder.append("Object does not escape method.\n");
						builder.append("Heap allocation has been eliminated.\n");

						String typeID = tag.getAttribute(ATTR_TYPE);

						String typeOrKlassName = null;

						if (typeID != null)
						{
							typeOrKlassName = ParseUtil.lookupType(typeID, parseDictionary);

							if (typeOrKlassName != null)
							{
								builder.append("Eliminated allocation was of type ").append(typeOrKlassName);
							}
						}

						bcAnnotations.addAnnotation(bciValue,
								new LineAnnotation(builder.toString(), BCAnnotationType.ELIMINATED_ALLOCATION));

						if (instr.getOpcode() == Opcode.NEW)
						{
							instr.setEliminated(true);
						}
						else
						{
							logger.warn("Found heap elimination on instruction that is not Opcode.NEW: {} @ {} ({}/{})",
									instr.getOpcode(), instr.getOffset(), typeID, typeOrKlassName);
						}
					}
				}
				catch (NumberFormatException nfe)
				{
					logger.error("Couldn't parse BCI", nfe);
				}
			}

		}
	}

	// 
	// 
	// 
	// 
	// 
	// 

	// JDK9 has much more detail in eliminate_lock
	// (callnode.cpp:log_lock_optimization)
	private void visitTagEliminateLock(Tag tag, IParseDictionary parseDictionary)
	{
		List childrenJVMS = tag.getNamedChildren(TAG_JVMS);

		if (childrenJVMS.size() > 0)
		{
			StringBuilder builder = new StringBuilder();

			builder.append("A lock has been eliminated").append(S_NEWLINE);
			builder.append("Call chain:").append(S_NEWLINE);

			int depth = 0;

			for (Tag tagJVMS : childrenJVMS)
			{
				String bci = tagJVMS.getAttribute(ATTR_BCI);

				if (bci != null)
				{
					try
					{
						int bciValue = Integer.parseInt(bci);

						String methodID = tagJVMS.getAttribute(ATTR_METHOD);

						if (methodID != null)
						{
							IMetaMember member = ParseUtil.lookupMember(methodID, parseDictionary, model);

							if (member != null)
							{
								if (bciValue != -1)
								{
									builder.append(StringUtil.repeat(C_SPACE, depth * 2)).append("->").append(C_SPACE);
									depth++;
								}

								builder.append(member.toStringUnqualifiedMethodName(true));
							}
						}

						builder.append(S_NEWLINE);

						if (bciValue != -1)
						{
							bcAnnotations.addAnnotation(bciValue,
									new LineAnnotation(builder.toString().trim(), BCAnnotationType.LOCK_ELISION));

							BytecodeInstruction instr = getInstructionAtIndex(bciValue);

							if (instr != null && instr.isLock())
							{
								instr.setEliminated(true);
							}
						}
					}
					catch (NumberFormatException nfe)
					{
						logger.error("Couldn't parse BCI", nfe);
					}
				}
			}
		}
	}

	private void visitTagUncommonTrap(Tag tag)
	{
		UncommonTrap trap = UncommonTrap.parse(tag);

		if (trap != null)
		{
			bcAnnotations.addAnnotation(trap.getBCI(), new LineAnnotation(trap.toString(), BCAnnotationType.UNCOMMON_TRAP));
		}
	}

	private void buildParseTagAnnotations(Tag parseTag, BytecodeAnnotations annotations, CompilerName compilerName,
			IParseDictionary parseDictionary) throws AnnotationException
	{
		// Only interested in annotating the current method so
		// do not recurse into method or parse tags

		if (DEBUG_LOGGING)
		{
			logger.debug("Building parse tag annotations");
		}

		List children = parseTag.getChildren();

		int currentBytecode = -1;

		Map methodAttrs = new HashMap<>();
		Map callAttrs = new HashMap<>();

		String currentMethodID = parseTag.getAttribute(ATTR_METHOD);

		boolean isC2 = false;

		if (compilerName == CompilerName.C2)
		{
			isC2 = true;
		}

		boolean inMethod = true;
		BytecodeInstruction currentInstruction = null;

		for (Tag child : children)
		{
			String name = child.getName();
			Map tagAttrs = child.getAttributes();

			if (DEBUG_LOGGING)
			{
				logger.debug("Examining child tag {}", child);
			}

			switch (name)
			{
			case TAG_BC:
			{
				String bciAttr = tagAttrs.get(ATTR_BCI);
				String codeAttr = tagAttrs.get(ATTR_CODE);

				currentBytecode = Integer.parseInt(bciAttr);
				int code = Integer.parseInt(codeAttr);
				callAttrs.clear();

				// TODO fix this old logic

				// we found a LogCompilation bc tag
				// e.g. ""
				// Now check in the current class bytecode
				// that the instruction at offset bci
				// has the same opcode as attribute code
				// if not then this is probably a TieredCompilation
				// context change. (TieredCompilation does not use
				// nested parse tags so have to use this heuristic
				// to check if we are still in the same method.

				if (DEBUG_LOGGING_BYTECODE)
				{
					logger.debug("BC Tag {} {}", currentBytecode, code);
				}

				currentInstruction = getInstructionAtIndex(currentBytecode);

				if (DEBUG_LOGGING_BYTECODE)
				{
					logger.debug("Instruction at {} is {}", currentBytecode, currentInstruction);
				}

				inMethod = false;

				if (currentInstruction != null)
				{
					int opcodeValue = currentInstruction.getOpcode().getValue();

					if (opcodeValue == code)
					{
						inMethod = true;
					}
				}
				break;
			}

			case TAG_CALL:
			{
				callAttrs.clear();
				callAttrs.putAll(tagAttrs);

				break;
			}

			case TAG_METHOD:
			{
				methodAttrs.clear();
				methodAttrs.putAll(tagAttrs);

				String nameAttr = methodAttrs.get(ATTR_NAME);

				inMethod = false;

				if (nameAttr != null && currentInstruction != null && currentInstruction.hasComment())
				{
					String comment = currentInstruction.getComment();

					inMethod = comment.contains(nameAttr);
				}

				break;
			}

			case TAG_INLINE_SUCCESS:
			{
				if (inMethod || isC2)
				{
					if (!sanityCheckInline(currentInstruction))
					{
						throw new AnnotationException("Expected an invoke instruction (in INLINE_SUCCESS)", currentBytecode,
								currentInstruction);
					}

					String reason = tagAttrs.get(ATTR_REASON);
					String annotationText = TooltipUtil.buildInlineAnnotationText(true, reason, callAttrs, methodAttrs,
							parseDictionary);

					bcAnnotations.addAnnotation(currentBytecode,
							new LineAnnotation(annotationText, BCAnnotationType.INLINE_SUCCESS));
				}

				break;
			}

			case TAG_INLINE_FAIL:
			{
				if (inMethod || isC2)
				{
					if (!sanityCheckInline(currentInstruction))
					{
						throw new AnnotationException("Expected an invoke instruction (in INLINE_FAIL)", currentBytecode,
								currentInstruction);
					}

					String reason = tagAttrs.get(ATTR_REASON);
					String annotationText = TooltipUtil.buildInlineAnnotationText(false, reason, callAttrs, methodAttrs,
							parseDictionary);

					bcAnnotations.addAnnotation(currentBytecode, new LineAnnotation(annotationText, BCAnnotationType.INLINE_FAIL));
				}

				break;
			}

			case TAG_BRANCH:
			{
				if (!bcAnnotations.hasAnnotationsForBCI(currentBytecode))
				{
					if (inMethod || isC2)
					{
						if (!sanityCheckBranch(currentInstruction))
						{
							throw new AnnotationException("Expected a branch instruction (BRANCH)", currentBytecode,
									currentInstruction);
						}

						String branchAnnotation = buildBranchAnnotation(tagAttrs);

						bcAnnotations.addAnnotation(currentBytecode, new LineAnnotation(branchAnnotation, BCAnnotationType.BRANCH));
					}
				}

				break;
			}

			case TAG_INTRINSIC:
			{
				if (inMethod || isC2)
				{
					if (!sanityCheckIntrinsic(currentInstruction))
					{
						throw new AnnotationException("Expected an invoke instruction (INTRINSIC)", currentBytecode,
								currentInstruction);
					}

					StringBuilder reason = new StringBuilder();
					reason.append("Intrinsic: ").append(tagAttrs.get(ATTR_ID));

					bcAnnotations.addAnnotation(currentBytecode,
							new LineAnnotation(reason.toString(), BCAnnotationType.INTRINSIC_USED));
				}

				break;
			}

			case TAG_UNCOMMON_TRAP:
			{
				String trapMethod = child.getAttribute(ATTR_METHOD);

				if (trapMethod == null || currentMethodID.equals(trapMethod))
				{
					visitTagUncommonTrap(child);
				}

				break;
			}

			case TAG_PHASE:
			{
				String phaseName = tagAttrs.get(ATTR_NAME);

				if (S_PARSE_HIR.equals(phaseName))
				{
					buildParseTagAnnotations(child, annotations, compilerName, parseDictionary);
				}
				else
				{
					logger.warn("Don't know how to handle phase {}", phaseName);
				}

				break;
			}

			default:
				handleOther(child);
				break;
			}
		}
	}

	private String buildBranchAnnotation(Map tagAttrs)
	{
		String count = tagAttrs.get(ATTR_BRANCH_COUNT);
		String taken = tagAttrs.get(ATTR_BRANCH_TAKEN);
		String notTaken = tagAttrs.get(ATTR_BRANCH_NOT_TAKEN);
		String prob = tagAttrs.get(ATTR_BRANCH_PROB);

		StringBuilder reason = new StringBuilder();

		if (count != null)
		{
			reason.append("Count: ").append(count).append(C_NEWLINE);
		}

		reason.append("Branch taken: ").append(taken).append(C_NEWLINE).append("Branch not taken: ").append(notTaken);

		if (prob != null)
		{
			reason.append(C_NEWLINE).append("Taken Probability: ").append(prob);
		}

		return reason.toString();
	}

	// used to detect if class file matches log file
	// only these bytecode instruction should be at the offset
	// for an inlining log statement
	public static boolean sanityCheckInline(BytecodeInstruction instr)
	{
		return sanityCheckInvoke(instr);
	}

	public static boolean sanityCheckIntrinsic(BytecodeInstruction instr)
	{
		return sanityCheckInvoke(instr);
	}

	private static boolean sanityCheckInvoke(BytecodeInstruction instr)
	{
		boolean sane = false;

		if (instr != null)
		{
			sane = instr.isInvoke();
		}

		return sane;
	}

	public static boolean sanityCheckBranch(BytecodeInstruction instr)
	{
		boolean sane = false;

		if (instr != null)
		{
			sane = instr.getOpcode().getMnemonic().startsWith("if");
		}

		return sane;
	}

	private BytecodeInstruction getInstructionAtIndex(int index)
	{
		BytecodeInstruction found = null;

		for (BytecodeInstruction instruction : member.getInstructions())
		{
			if (instruction.getOffset() == index)
			{
				found = instruction;
				break;
			}
		}

		return found;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy