WASM: support stack traces

This commit is contained in:
Alexey Andreev 2017-04-06 00:00:42 +03:00
parent 09b3f18a2c
commit 8330eae4ae
15 changed files with 241 additions and 23 deletions

View File

@ -21,10 +21,14 @@ import java.util.Map;
import org.teavm.classlib.impl.DeclaringClassMetadataGenerator;
import org.teavm.classlib.java.lang.annotation.TAnnotation;
import org.teavm.classlib.java.lang.reflect.TAnnotatedElement;
import org.teavm.interop.Address;
import org.teavm.interop.DelegateTo;
import org.teavm.platform.Platform;
import org.teavm.platform.PlatformClass;
import org.teavm.platform.metadata.ClassResource;
import org.teavm.platform.metadata.ClassScopedMetadataProvider;
import org.teavm.runtime.RuntimeClass;
import org.teavm.runtime.RuntimeJavaObject;
public class TClass<T> extends TObject implements TAnnotatedElement {
TString name;
@ -61,6 +65,7 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
return Platform.isAssignable(obj.getPlatformClass(), platformClass);
}
@DelegateTo("getNameLowLevel")
public TString getName() {
if (name == null) {
name = TString.wrap(Platform.getName(platformClass));
@ -68,6 +73,11 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
return name;
}
private RuntimeJavaObject getNameLowLevel() {
RuntimeClass runtimeClass = Address.ofObject(this).toStructure();
return runtimeClass.name;
}
public TString getSimpleName() {
if (simpleName == null) {
if (isArray()) {

View File

@ -19,10 +19,6 @@ import java.util.Objects;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.util.TObjects;
/**
*
* @author Alexey Andreev
*/
public final class TStackTraceElement extends TObject implements TSerializable {
private TString declaringClass;
private TString methodName;
@ -89,6 +85,7 @@ public final class TStackTraceElement extends TObject implements TSerializable {
} else {
sb.append(TString.wrap("Unknown Source"));
}
sb.append(TString.wrap(")"));
return sb.toString();
}
}

View File

@ -17,9 +17,11 @@ package org.teavm.classlib.java.lang;
import org.teavm.classlib.java.io.TPrintStream;
import org.teavm.classlib.java.util.TArrays;
import org.teavm.interop.DelegateTo;
import org.teavm.interop.Remove;
import org.teavm.interop.Rename;
import org.teavm.interop.Superclass;
import org.teavm.runtime.ExceptionHandling;
@Superclass("java.lang.Object")
public class TThrowable extends RuntimeException {
@ -29,6 +31,7 @@ public class TThrowable extends RuntimeException {
private boolean suppressionEnabled;
private boolean writableStackTrace;
private TThrowable[] suppressed = new TThrowable[0];
private TStackTraceElement[] stackTrace;
@SuppressWarnings("unused")
@Rename("fakeInit")
@ -97,10 +100,18 @@ public class TThrowable extends RuntimeException {
}
@Override
@DelegateTo("fillInStackTraceLowLevel")
public Throwable fillInStackTrace() {
return this;
}
private TThrowable fillInStackTraceLowLevel() {
int stackSize = ExceptionHandling.callStackSize() - 1;
stackTrace = new TStackTraceElement[stackSize];
ExceptionHandling.fillStackTrace((StackTraceElement[]) (Object) stackTrace, 2);
return this;
}
@Rename("getMessage")
public TString getMessage0() {
return message;
@ -143,15 +154,25 @@ public class TThrowable extends RuntimeException {
public void printStackTrace(TPrintStream stream) {
stream.println(TString.wrap(getClass().getName() + ": " + getMessage()));
if (stackTrace != null) {
for (TStackTraceElement element : stackTrace) {
stream.print(TString.wrap(" at "));
stream.println(element);
}
}
if (cause != null && cause != this) {
stream.print(TString.wrap("Caused by: "));
cause.printStackTrace(stream);
}
}
@Rename("getStackTrace")
public TStackTraceElement[] getStackTrace0() {
return new TStackTraceElement[0];
return stackTrace != null ? stackTrace.clone() : new TStackTraceElement[0];
}
public void setStackTrace(@SuppressWarnings("unused") TStackTraceElement[] stackTrace) {
// do nothing
this.stackTrace = stackTrace.clone();
}
@Rename("getSuppressed")

View File

@ -282,7 +282,7 @@ public class WasmTarget implements TeaVMTarget {
Decompiler decompiler = new Decompiler(classes, controller.getClassLoader(), new HashSet<>(),
new HashSet<>());
WasmStringPool stringPool = new WasmStringPool(classGenerator, binaryWriter);
WasmStringPool stringPool = classGenerator.getStringPool();
WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(),
vtableProvider, tagRegistry, stringPool);
@ -302,7 +302,7 @@ public class WasmTarget implements TeaVMTarget {
context.addIntrinsic(mutatorIntrinsic);
context.addIntrinsic(new ShadowStackIntrinsic());
ExceptionHandlingIntrinsic exceptionHandlingIntrinsic = new ExceptionHandlingIntrinsic(binaryWriter,
classGenerator);
classGenerator, stringPool);
context.addIntrinsic(exceptionHandlingIntrinsic);
WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator, binaryWriter);

View File

@ -16,34 +16,50 @@
package org.teavm.backend.wasm.generate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.teavm.backend.wasm.binary.BinaryWriter;
import org.teavm.backend.wasm.binary.DataPrimitives;
import org.teavm.backend.wasm.binary.DataStructure;
import org.teavm.backend.wasm.binary.DataValue;
import org.teavm.model.ValueType;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.model.lowlevel.CallSiteLocation;
import org.teavm.model.lowlevel.ExceptionHandlerDescriptor;
public class CallSiteBinaryGenerator {
private static final int CALL_SITE_HANDLER_COUNT = 0;
private static final int CALL_SITE_FIRST_HANDLER = 1;
private static final int CALL_SITE_LOCATION = 2;
private static final int EXCEPTION_HANDLER_ID = 0;
private static final int EXCEPTION_HANDLER_CLASS = 1;
private static final int LOCATION_FILE = 0;
private static final int LOCATION_CLASS = 1;
private static final int LOCATION_METHOD = 2;
private static final int LOCATION_LINE_NUMBER = 3;
private DataStructure callSiteStructure = new DataStructure((byte) 0,
DataPrimitives.INT,
DataPrimitives.ADDRESS,
DataPrimitives.ADDRESS);
private DataStructure exceptionHandlerStructure = new DataStructure((byte) 0,
DataPrimitives.INT,
DataPrimitives.ADDRESS);
private DataStructure locationStructure = new DataStructure((byte) 0,
DataPrimitives.ADDRESS,
DataPrimitives.ADDRESS,
DataPrimitives.ADDRESS,
DataPrimitives.INT);
private BinaryWriter writer;
private WasmClassGenerator classGenerator;
private WasmStringPool stringPool;
public CallSiteBinaryGenerator(BinaryWriter writer, WasmClassGenerator classGenerator) {
public CallSiteBinaryGenerator(BinaryWriter writer, WasmClassGenerator classGenerator, WasmStringPool stringPool) {
this.writer = writer;
this.classGenerator = classGenerator;
this.stringPool = stringPool;
}
public int writeCallSites(List<CallSiteDescriptor> callSites) {
@ -53,7 +69,7 @@ public class CallSiteBinaryGenerator {
int firstCallSite = -1;
List<DataValue> binaryCallSites = new ArrayList<>();
for (CallSiteDescriptor callSite : callSites) {
for (int i = 0; i < callSites.size(); i++) {
DataValue binaryCallSite = callSiteStructure.createValue();
int address = writer.append(binaryCallSite);
if (firstCallSite < 0) {
@ -62,6 +78,8 @@ public class CallSiteBinaryGenerator {
binaryCallSites.add(binaryCallSite);
}
Map<CallSiteLocation, Integer> locationCache = new HashMap<>();
for (int callSiteId = 0; callSiteId < callSites.size(); ++callSiteId) {
DataValue binaryCallSite = binaryCallSites.get(callSiteId);
CallSiteDescriptor callSite = callSites.get(callSiteId);
@ -91,6 +109,23 @@ public class CallSiteBinaryGenerator {
binaryExceptionHandler.setAddress(EXCEPTION_HANDLER_CLASS, classPointer);
}
}
int locationAddress = locationCache.computeIfAbsent(callSite.getLocation(), location -> {
DataValue binaryLocation = locationStructure.createValue();
int address = writer.append(binaryLocation);
if (location.getFileName() != null) {
binaryLocation.setAddress(LOCATION_FILE, stringPool.getStringPointer(location.getFileName()));
}
if (location.getClassName() != null) {
binaryLocation.setAddress(LOCATION_CLASS, stringPool.getStringPointer(location.getClassName()));
}
if (location.getMethodName() != null) {
binaryLocation.setAddress(LOCATION_METHOD, stringPool.getStringPointer(location.getMethodName()));
}
binaryLocation.setInt(LOCATION_LINE_NUMBER, location.getLineNumber());
return address;
});
binaryCallSite.setAddress(CALL_SITE_LOCATION, locationAddress);
}
return firstCallSite;

View File

@ -57,6 +57,7 @@ public class WasmClassGenerator {
private List<String> functionTable = new ArrayList<>();
private VirtualTableProvider vtableProvider;
private TagRegistry tagRegistry;
private WasmStringPool stringPool;
private DataStructure objectStructure = new DataStructure((byte) 0,
DataPrimitives.INT, /* class */
DataPrimitives.ADDRESS /* monitor/hash code */);
@ -67,6 +68,7 @@ public class WasmClassGenerator {
DataPrimitives.INT, /* flags */
DataPrimitives.INT, /* tag */
DataPrimitives.INT, /* canary */
DataPrimitives.ADDRESS, /* name */
DataPrimitives.ADDRESS, /* item type */
DataPrimitives.ADDRESS, /* array type */
DataPrimitives.INT, /* isInstance function */
@ -79,11 +81,12 @@ public class WasmClassGenerator {
private static final int CLASS_FLAGS = 2;
private static final int CLASS_TAG = 3;
private static final int CLASS_CANARY = 4;
private static final int CLASS_ITEM_TYPE = 5;
private static final int CLASS_ARRAY_TYPE = 6;
private static final int CLASS_IS_INSTANCE = 7;
private static final int CLASS_PARENT = 8;
private static final int CLASS_LAYOUT = 9;
private static final int CLASS_NAME = 5;
private static final int CLASS_ITEM_TYPE = 6;
private static final int CLASS_ARRAY_TYPE = 7;
private static final int CLASS_IS_INSTANCE = 8;
private static final int CLASS_PARENT = 9;
private static final int CLASS_LAYOUT = 10;
public WasmClassGenerator(ClassReaderSource classSource, VirtualTableProvider vtableProvider,
TagRegistry tagRegistry, BinaryWriter binaryWriter) {
@ -91,6 +94,11 @@ public class WasmClassGenerator {
this.vtableProvider = vtableProvider;
this.tagRegistry = tagRegistry;
this.binaryWriter = binaryWriter;
this.stringPool = new WasmStringPool(this, binaryWriter);
}
public WasmStringPool getStringPool() {
return stringPool;
}
private void addClass(ValueType type) {
@ -204,6 +212,7 @@ public class WasmClassGenerator {
int tag = ranges.stream().mapToInt(range -> range.lower).min().orElse(0);
header.setInt(CLASS_TAG, tag);
header.setInt(CLASS_CANARY, RuntimeClass.computeCanary(occupiedSize, tag));
header.setAddress(CLASS_NAME, stringPool.getStringPointer(name));
header.setInt(CLASS_IS_INSTANCE, functionTable.size());
functionTable.add(WasmMangling.mangleIsSupertype(ValueType.object(name)));
header.setAddress(CLASS_PARENT, parentPtr);

View File

@ -21,6 +21,7 @@ import org.teavm.ast.InvocationExpr;
import org.teavm.backend.wasm.binary.BinaryWriter;
import org.teavm.backend.wasm.generate.CallSiteBinaryGenerator;
import org.teavm.backend.wasm.generate.WasmClassGenerator;
import org.teavm.backend.wasm.generate.WasmStringPool;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.backend.wasm.model.expression.WasmIntBinary;
@ -28,14 +29,18 @@ import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmIntType;
import org.teavm.model.MethodReference;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.runtime.CallSite;
import org.teavm.runtime.ExceptionHandling;
public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
private CallSiteBinaryGenerator callSiteBinaryGenerator;
private WasmClassGenerator classGenerator;
private List<WasmInt32Constant> constants = new ArrayList<>();
public ExceptionHandlingIntrinsic(BinaryWriter binaryWriter, WasmClassGenerator classGenerator) {
callSiteBinaryGenerator = new CallSiteBinaryGenerator(binaryWriter, classGenerator);
public ExceptionHandlingIntrinsic(BinaryWriter binaryWriter, WasmClassGenerator classGenerator,
WasmStringPool stringPool) {
callSiteBinaryGenerator = new CallSiteBinaryGenerator(binaryWriter, classGenerator, stringPool);
this.classGenerator = classGenerator;
}
@Override
@ -63,9 +68,10 @@ public class ExceptionHandlingIntrinsic implements WasmIntrinsic {
constant.setLocation(invocation.getLocation());
constants.add(constant);
int callSiteSize = classGenerator.getClassSize(CallSite.class.getName());
WasmExpression id = manager.generate(invocation.getArguments().get(0));
WasmExpression offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.SHL,
id, new WasmInt32Constant(3));
WasmExpression offset = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.MUL,
id, new WasmInt32Constant(callSiteSize));
return new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, constant, offset);
}

View File

@ -21,15 +21,21 @@ import java.util.List;
public class CallSiteDescriptor {
private int id;
private List<ExceptionHandlerDescriptor> handlers = new ArrayList<>();
private CallSiteLocation location;
public CallSiteDescriptor(int id) {
public CallSiteDescriptor(int id, CallSiteLocation location) {
this.id = id;
this.location = location;
}
public int getId() {
return id;
}
public CallSiteLocation getLocation() {
return location;
}
public List<ExceptionHandlerDescriptor> getHandlers() {
return handlers;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.model.lowlevel;
import java.util.Objects;
public class CallSiteLocation {
private String fileName;
private String className;
private String methodName;
private int lineNumber;
public CallSiteLocation(String fileName, String className, String methodName, int lineNumber) {
this.fileName = fileName;
this.className = className;
this.methodName = methodName;
this.lineNumber = lineNumber;
}
public String getFileName() {
return fileName;
}
public String getClassName() {
return className;
}
public String getMethodName() {
return methodName;
}
public int getLineNumber() {
return lineNumber;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CallSiteLocation)) {
return false;
}
CallSiteLocation other = (CallSiteLocation) obj;
return Objects.equals(fileName, other.fileName) && Objects.equals(className, other.className)
&& Objects.equals(methodName, other.methodName) && lineNumber == other.lineNumber;
}
@Override
public int hashCode() {
return Objects.hash(fileName, className, methodName, lineNumber);
}
}

View File

@ -190,7 +190,11 @@ public class ExceptionHandlingShadowStackContributor {
}
}
CallSiteDescriptor callSite = new CallSiteDescriptor(callSites.size());
String fileName = insn.getLocation() != null ? insn.getLocation().getFileName() : null;
int lineNumber = insn.getLocation() != null ? insn.getLocation().getLine() : -1;
CallSiteLocation location = new CallSiteLocation(fileName, method.getClassName(), method.getName(),
lineNumber);
CallSiteDescriptor callSite = new CallSiteDescriptor(callSites.size(), location);
callSites.add(callSite);
List<Instruction> pre = setLocation(getInstructionsBeforeCallSite(callSite), insn.getLocation());
List<Instruction> post = getInstructionsAfterCallSite(block, next, callSite, currentJointSources);

View File

@ -24,4 +24,5 @@ import org.teavm.interop.Unmanaged;
public class CallSite extends Structure {
public int handlerCount;
public ExceptionHandler firstHandler;
public CallSiteLocation location;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.runtime;
import org.teavm.interop.Structure;
public class CallSiteLocation extends Structure {
public String fileName;
public String className;
public String methodName;
public int lineNumber;
}

View File

@ -21,7 +21,6 @@ import org.teavm.interop.StaticInit;
import org.teavm.interop.Structure;
import org.teavm.interop.Unmanaged;
@Unmanaged
@StaticInit
public final class ExceptionHandling {
private ExceptionHandling() {
@ -32,12 +31,14 @@ public final class ExceptionHandling {
private static Throwable thrownException;
@Export(name = "sys$catchException")
@Unmanaged
public static Throwable catchException() {
Throwable exception = thrownException;
thrownException = null;
return exception;
}
@Unmanaged
public static void throwException(Throwable exception) {
thrownException = exception;
@ -64,4 +65,41 @@ public final class ExceptionHandling {
stackFrame = ShadowStack.getNextStackFrame(stackFrame);
}
}
@Unmanaged
public static int callStackSize() {
Address stackFrame = ShadowStack.getStackTop();
int size = 0;
while (stackFrame != null) {
stackFrame = ShadowStack.getNextStackFrame(stackFrame);
size++;
}
return size;
}
@Unmanaged
public static void fillStackTrace(StackTraceElement[] target, int skip) {
Address stackFrame = ShadowStack.getNextStackFrame(ShadowStack.getNextStackFrame(ShadowStack.getStackTop()));
int index = 0;
while (stackFrame != null && index < target.length) {
int callSiteId = ShadowStack.getCallSiteId(stackFrame);
CallSite callSite = findCallSiteById(callSiteId);
CallSiteLocation location = callSite.location;
StackTraceElement element = createElement(location != null ? location.className : "",
location != null ? location.methodName : "", location != null ? location.fileName : null,
location != null ? location.lineNumber : -1);
if (skip > 0) {
skip--;
} else {
target[index++] = element;
}
stackFrame = ShadowStack.getNextStackFrame(stackFrame);
}
}
private static StackTraceElement createElement(String className, String methodName, String fileName,
int lineNumber) {
return new StackTraceElement(className, methodName, fileName, lineNumber);
}
}

View File

@ -26,6 +26,7 @@ public class RuntimeClass extends RuntimeJavaObject {
public int flags;
public int tag;
public int canary;
public RuntimeJavaObject name;
public RuntimeClass itemType;
public RuntimeClass arrayType;
public IsSupertypeFunction isSupertypeOf;

View File

@ -172,7 +172,6 @@ public final class Platform {
return cls.itemType;
}
@DelegateTo("getNameLowLevel")
public static String getName(PlatformClass cls) {
return cls.getMetadata().getName();
}