package org.graalvm.wasm;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import org.graalvm.wasm.collection.IntArrayList;
import org.graalvm.wasm.constants.GlobalModifier;
import java.util.ArrayList;
import java.util.List;
import static com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import static com.oracle.truffle.api.CompilerDirectives.transferToInterpreter;
@ExportLibrary(InteropLibrary.class)
@SuppressWarnings("static-method")
public final class WasmInstance extends RuntimeState implements TruffleObject {
public WasmInstance(WasmModule module) {
super(module);
}
public String name() {
return module().name();
}
public WasmFunctionInstance inferEntryPoint() {
final WasmFunction mainFunction = symbolTable().exportedFunctions().get("_main");
if (mainFunction != null) {
return functionInstance(mainFunction);
}
final WasmFunction startFunction = symbolTable().exportedFunctions().get("_start");
if (startFunction != null) {
return functionInstance(startFunction);
}
if (symbolTable().startFunction() != null) {
return functionInstance(symbolTable().startFunction());
}
return null;
}
private WasmFunctionInstance functionInstance(WasmFunction function) {
return new WasmFunctionInstance(function, target(function.index()));
}
private void ensureLinked() {
WasmContext.getCurrent().linker().tryLink(this);
}
@ExportMessage
boolean hasMembers() {
return true;
}
@ExportMessage
@TruffleBoundary
public Object readMember(String member) throws UnknownIdentifierException {
ensureLinked();
final SymbolTable symbolTable = symbolTable();
final WasmFunction function = symbolTable.exportedFunctions().get(member);
if (function != null) {
return functionInstance(function);
}
final Integer globalIndex = symbolTable.exportedGlobals().get(member);
if (globalIndex != null) {
readGlobal(this, symbolTable, globalIndex);
}
if (member.equals(symbolTable.exportedMemory())) {
return memory();
}
throw UnknownIdentifierException.create(member);
}
@ExportMessage
@TruffleBoundary
public void writeMember(String member, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
ensureLinked();
final SymbolTable symbolTable = symbolTable();
final Integer index = symbolTable.exportedGlobals().get(member);
if (index == null) {
throw UnknownIdentifierException.create(member);
}
final int address = globalAddress(index);
if (!(value instanceof Number)) {
throw UnsupportedMessageException.create();
}
final boolean mutable = symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
if (module().isParsed() && !mutable) {
throw UnsupportedMessageException.create();
}
long longValue = ((Number) value).longValue();
WasmContext.getCurrent().globals().storeLong(address, longValue);
}
@ExportMessage
@TruffleBoundary
boolean isMemberReadable(String member) {
ensureLinked();
final SymbolTable symbolTable = symbolTable();
try {
return symbolTable.exportedFunctions().containsKey(member) || symbolTable.exportedGlobals().containsKey(member) ||
member.equals(symbolTable.exportedMemory());
} catch (NumberFormatException exc) {
return false;
}
}
@ExportMessage
@TruffleBoundary
boolean isMemberModifiable(String member) {
ensureLinked();
final SymbolTable symbolTable = symbolTable();
final Integer index = symbolTable.exportedGlobals().get(member);
if (index == null) {
return false;
}
final boolean mutable = symbolTable.globalMutability(index) == GlobalModifier.MUTABLE;
if (!mutable) {
return false;
}
return true;
}
@ExportMessage
@TruffleBoundary
boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
return false;
}
private static Object readGlobal(WasmInstance instance, SymbolTable symbolTable, int globalIndex) {
final int address = instance.globalAddress(globalIndex);
final GlobalRegistry globals = WasmContext.getCurrent().globals();
final byte type = symbolTable.globalValueType(globalIndex);
switch (type) {
case WasmType.I32_TYPE:
return globals.loadAsInt(address);
case WasmType.I64_TYPE:
return globals.loadAsLong(address);
case WasmType.F32_TYPE:
return globals.loadAsFloat(address);
case WasmType.F64_TYPE:
return globals.loadAsDouble(address);
default:
throw new RuntimeException("Unknown type: " + type);
}
}
@ExportMessage
@TruffleBoundary
Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
ensureLinked();
return new ExportedMembers(this, symbolTable());
}
public boolean isBuiltin() {
return data() == null;
}
@ExportLibrary(InteropLibrary.class)
static final class ExportedMembers implements TruffleObject {
private final WasmInstance instance;
private final SymbolTable symbolTable;
private final List<WasmFunction> exportedFunctions;
private final IntArrayList exportedGlobals;
ExportedMembers(WasmInstance instance, SymbolTable symbolTable) {
this.instance = instance;
this.symbolTable = symbolTable;
this.exportedFunctions = new ArrayList<>(symbolTable.exportedFunctions().values());
this.exportedGlobals = new IntArrayList();
for (int globalIndex : symbolTable.exportedGlobals().values()) {
this.exportedGlobals.add(globalIndex);
}
}
@ExportMessage
@TruffleBoundary
boolean hasArrayElements() {
return true;
}
@ExportMessage
@TruffleBoundary
boolean isArrayElementReadable(long index) {
return index >= 0 && index < getArraySize();
}
@ExportMessage
@TruffleBoundary
long getArraySize() {
return exportedFunctions.size() + exportedGlobals.size() + memoriesSize();
}
private int memoriesSize() {
return (symbolTable.exportedMemory() != null ? 1 : 0);
}
@ExportMessage
@TruffleBoundary
Object readArrayElement(long absoluteIndex) throws InvalidArrayIndexException {
long index = absoluteIndex;
if (!isArrayElementReadable(index)) {
transferToInterpreter();
throw InvalidArrayIndexException.create(index);
}
if (index < exportedFunctions.size()) {
return exportedFunctions.get((int) index);
}
index -= exportedFunctions.size();
if (index < exportedGlobals.size()) {
final int globalIndex = exportedGlobals.get((int) index);
return readGlobal(instance, symbolTable, globalIndex);
}
return instance.memory();
}
}
@Override
public String toString() {
return "wasm-module-instance(" + name() + ")";
}
}