package com.oracle.truffle.js.runtime.builtins;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
public final class JSFunctionData {
@CompilationFinal private volatile CallTarget callTarget;
@CompilationFinal private volatile CallTarget constructTarget;
@CompilationFinal private volatile CallTarget constructNewTarget;
private final JSContext context;
@CompilationFinal private String name;
private final int length;
private final int flags;
private static final int IS_CONSTRUCTOR = 1 << 0;
private static final int IS_DERIVED = 1 << 1;
private static final int IS_STRICT = 1 << 2;
private static final int IS_BUILTIN = 1 << 3;
private static final int NEEDS_PARENT_FRAME = 1 << 4;
private static final int IS_GENERATOR = 1 << 5;
private static final int IS_ASYNC = 1 << 6;
private static final int IS_CLASS_CONSTRUCTOR = 1 << 7;
private static final int STRICT_FUNCTION_PROPERTIES = 1 << 8;
private static final int NEEDS_NEW_TARGET = 1 << 9;
private static final int IS_BOUND = 1 << 10;
private volatile CallTarget rootTarget;
private volatile Initializer lazyInit;
private static final AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> UPDATER_CALL_TARGET =
AtomicReferenceFieldUpdater.newUpdater(JSFunctionData.class, CallTarget.class, "callTarget");
private static final AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> UPDATER_CONSTRUCT_TARGET =
AtomicReferenceFieldUpdater.newUpdater(JSFunctionData.class, CallTarget.class, "constructTarget");
private static final AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> UPDATER_CONSTRUCT_NEW_TARGET =
AtomicReferenceFieldUpdater.newUpdater(JSFunctionData.class, CallTarget.class, "constructNewTarget");
private static final AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> UPDATER_ROOT_TARGET =
AtomicReferenceFieldUpdater.newUpdater(JSFunctionData.class, CallTarget.class, "rootTarget");
private JSFunctionData(JSContext context, CallTarget callTarget, CallTarget constructTarget, CallTarget constructNewTarget, int length, String name, int flags) {
this.context = context;
this.callTarget = callTarget;
this.constructTarget = constructTarget;
this.constructNewTarget = constructNewTarget;
this.name = name;
this.length = length;
this.flags = flags;
}
public static JSFunctionData create(JSContext context, CallTarget callTarget, CallTarget constructTarget, CallTarget constructNewTarget, int length, String name, int flags) {
return new JSFunctionData(context, callTarget, constructTarget, constructNewTarget, length, name, flags);
}
public static JSFunctionData create(JSContext context, CallTarget callTarget, CallTarget constructTarget, CallTarget constructNewTarget, int length, String name, boolean isConstructor,
boolean isDerived, boolean isStrict, boolean isBuiltin, boolean needsParentFrame, boolean isGenerator, boolean isAsync, boolean isClassConstructor,
boolean strictFunctionProperties, boolean needsNewTarget, boolean isBound) {
int flags = (isConstructor ? IS_CONSTRUCTOR : 0) | (isDerived ? IS_DERIVED : 0) | (isStrict ? IS_STRICT : 0) | (isBuiltin ? IS_BUILTIN : 0) |
(needsParentFrame ? NEEDS_PARENT_FRAME : 0) | (isGenerator ? IS_GENERATOR : 0) | (isAsync ? IS_ASYNC : 0) | (isClassConstructor ? IS_CLASS_CONSTRUCTOR : 0) |
(strictFunctionProperties ? STRICT_FUNCTION_PROPERTIES : 0) | (needsNewTarget ? NEEDS_NEW_TARGET : 0) | (isBound ? IS_BOUND : 0);
return create(context, callTarget, constructTarget, constructNewTarget, length, name, flags);
}
public static JSFunctionData create(JSContext context, CallTarget callTarget, CallTarget constructTarget, int length, String name, boolean isConstructor, boolean isDerived, boolean strictMode,
boolean isBuiltin) {
assert callTarget != null && constructTarget != null;
return create(context, callTarget, constructTarget, constructTarget, length, name, isConstructor, isDerived, strictMode, isBuiltin, false, false, false, false,
hasStrictProperties(context, strictMode, isBuiltin), false, false);
}
public static JSFunctionData createCallOnly(JSContext context, CallTarget callTarget, int length, String name) {
assert callTarget != null;
CallTarget constructTarget = context.getNotConstructibleCallTarget();
return create(context, callTarget, constructTarget, length, name, false, false, false, true);
}
public static JSFunctionData create(JSContext context, int length, String name, boolean isConstructor, boolean isDerived, boolean strictMode, boolean isBuiltin) {
return create(context, null, null, null, length, name, isConstructor, isDerived, strictMode, isBuiltin, false, false, false, false,
hasStrictProperties(context, strictMode, isBuiltin), false, false);
}
public static JSFunctionData create(JSContext context, CallTarget callTarget, int length, String name) {
assert callTarget != null;
return create(context, callTarget, callTarget, callTarget, length, name, true, false, false, false, false, false, false, false, false, false, false);
}
private static boolean hasStrictProperties(JSContext context, boolean strictMode, boolean isBuiltin) {
return isBuiltin ? context.getEcmaScriptVersion() >= 6 : strictMode;
}
public CallTarget getCallTarget() {
CallTarget result = callTarget;
if (result != null) {
return result;
}
CompilerDirectives.transferToInterpreterAndInvalidate();
return ensureInitialized(Target.Call);
}
public CallTarget getConstructTarget() {
CallTarget result = constructTarget;
if (result != null) {
return result;
}
CompilerDirectives.transferToInterpreterAndInvalidate();
return ensureInitialized(Target.Construct);
}
public CallTarget getConstructNewTarget() {
CallTarget result = constructNewTarget;
if (result != null) {
return result;
}
CompilerDirectives.transferToInterpreterAndInvalidate();
return ensureInitialized(Target.ConstructNewTarget);
}
public JSContext getContext() {
return context;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLength() {
return length;
}
public boolean isConstructor() {
return (flags & IS_CONSTRUCTOR) != 0;
}
public boolean isStrict() {
return (flags & IS_STRICT) != 0;
}
public boolean hasStrictFunctionProperties() {
return (flags & STRICT_FUNCTION_PROPERTIES) != 0;
}
public boolean isBuiltin() {
return (flags & IS_BUILTIN) != 0;
}
public boolean needsParentFrame() {
return (flags & NEEDS_PARENT_FRAME) != 0;
}
public boolean isGenerator() {
return (flags & IS_GENERATOR) != 0;
}
public boolean isAsync() {
return (flags & IS_ASYNC) != 0;
}
public boolean isAsyncGenerator() {
return isGenerator() && isAsync();
}
public boolean isDerived() {
return (flags & IS_DERIVED) != 0;
}
public boolean isClassConstructor() {
return (flags & IS_CLASS_CONSTRUCTOR) != 0;
}
public boolean isPrototypeNotWritable() {
return isClassConstructor();
}
public boolean requiresNew() {
return isClassConstructor();
}
public boolean needsNewTarget() {
return (flags & NEEDS_NEW_TARGET) != 0;
}
public boolean isBound() {
return (flags & IS_BOUND) != 0;
}
public int getFlags() {
return flags;
}
public CallTarget getCallTarget(BranchProfile initBranch) {
CallTarget result = callTarget;
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.FASTPATH_PROBABILITY, result != null)) {
return result;
}
initBranch.enter();
return (CallTarget) ensureInitializedCall();
}
public CallTarget getConstructTarget(BranchProfile initBranch) {
CallTarget result = constructTarget;
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.FASTPATH_PROBABILITY, result != null)) {
return result;
}
initBranch.enter();
return (CallTarget) ensureInitializedConstruct();
}
public CallTarget getConstructNewTarget(BranchProfile initBranch) {
CallTarget result = constructNewTarget;
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.FASTPATH_PROBABILITY, result != null)) {
return result;
}
initBranch.enter();
return (CallTarget) ensureInitializedConstructNewTarget();
}
@TruffleBoundary
private Object ensureInitializedCall() {
return ensureInitialized(Target.Call);
}
@TruffleBoundary
private Object ensureInitializedConstruct() {
return ensureInitialized(Target.Construct);
}
@TruffleBoundary
private Object ensureInitializedConstructNewTarget() {
return ensureInitialized(Target.ConstructNewTarget);
}
public CallTarget setCallTarget(CallTarget callTarget) {
return setAndGetCallTarget(UPDATER_CALL_TARGET, callTarget);
}
public CallTarget setConstructTarget(CallTarget constructTarget) {
return setAndGetCallTarget(UPDATER_CONSTRUCT_TARGET, constructTarget);
}
public CallTarget setConstructNewTarget(CallTarget constructNewTarget) {
return setAndGetCallTarget(UPDATER_CONSTRUCT_NEW_TARGET, constructNewTarget);
}
public CallTarget setRootTarget(CallTarget rootTarget) {
CompilerAsserts.neverPartOfCompilation();
Objects.requireNonNull(rootTarget);
if (UPDATER_ROOT_TARGET.compareAndSet(this, null, rootTarget)) {
return rootTarget;
} else {
throw Errors.shouldNotReachHere("call target created more than once");
}
}
private CallTarget setAndGetCallTarget(AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> updater, CallTarget newTarget) {
CompilerAsserts.neverPartOfCompilation();
Objects.requireNonNull(newTarget);
if (updater.compareAndSet(this, null, newTarget)) {
return newTarget;
} else {
return updater.get(this);
}
}
@Override
@TruffleBoundary
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "(" + name + ")";
}
public void setLazyInit(Initializer lazyInit) {
assert JSConfig.LazyFunctionData;
assert this.lazyInit == null;
this.lazyInit = lazyInit;
}
public boolean hasLazyInit() {
return lazyInit != null;
}
private CallTarget ensureInitialized(Target target) {
CompilerAsserts.neverPartOfCompilation();
Initializer init = lazyInit;
assert init != null;
CallTarget rootCallTarget = rootTarget;
if (rootCallTarget == null) {
synchronized (context) {
rootCallTarget = rootTarget;
if (rootCallTarget == null) {
init.initializeRoot(this);
rootCallTarget = rootTarget;
if (!(init instanceof CallTargetInitializer)) {
lazyInit = init = (CallTargetInitializer) ((RootCallTarget) rootCallTarget).getRootNode();
}
}
}
}
assert rootCallTarget != null;
AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> updater = target.getUpdater();
CallTarget result = updater.get(this);
if (result != null) {
return result;
}
CallTargetInitializer callTargetInit;
if (init instanceof CallTargetInitializer) {
callTargetInit = (CallTargetInitializer) init;
} else {
callTargetInit = (CallTargetInitializer) ((RootCallTarget) rootCallTarget).getRootNode();
}
callTargetInit.initializeCallTarget(this, target, rootCallTarget);
result = updater.get(this);
assert result != null;
return result;
}
public void materialize() {
CompilerAsserts.neverPartOfCompilation();
assert !isBuiltin();
Initializer init = lazyInit;
if (init != null && rootTarget == null) {
synchronized (context) {
if (rootTarget == null) {
init.initializeRoot(this);
}
}
}
}
public enum Target {
Call(UPDATER_CALL_TARGET),
Construct(UPDATER_CONSTRUCT_TARGET),
ConstructNewTarget(UPDATER_CONSTRUCT_NEW_TARGET);
private final AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> updater;
Target(AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> updater) {
this.updater = updater;
}
AtomicReferenceFieldUpdater<JSFunctionData, CallTarget> getUpdater() {
return updater;
}
}
public interface Initializer {
void initializeRoot(JSFunctionData functionData);
}
public interface CallTargetInitializer extends Initializer {
void initializeCallTarget(JSFunctionData functionData, Target target, CallTarget rootTarget);
default void initializeEager(JSFunctionData functionData) {
assert functionData.rootTarget == null;
initializeRoot(functionData);
CallTarget rootTarget = Objects.requireNonNull(functionData.rootTarget);
initializeCallTarget(functionData, Target.Call, rootTarget);
initializeCallTarget(functionData, Target.Construct, rootTarget);
initializeCallTarget(functionData, Target.ConstructNewTarget, rootTarget);
}
}
}