package com.oracle.truffle.js.nodes.access;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Executed;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.ExplodeLoop.LoopExplosionKind;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.function.SetFunctionNameNode;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.Undefined;
import java.util.Set;
public abstract class InitializeInstanceElementsNode extends JavaScriptNode {
@Child @Executed protected JavaScriptNode targetNode;
@Child @Executed protected JavaScriptNode constructorNode;
@Child @Executed(with = "constructorNode") protected JSTargetableNode fieldsNode;
@Child @Executed(with = "constructorNode") protected JSTargetableNode brandNode;
protected final JSContext context;
protected InitializeInstanceElementsNode(JSContext context, JavaScriptNode targetNode, JavaScriptNode constructorNode) {
this.context = context;
this.targetNode = targetNode;
this.constructorNode = constructorNode;
if (constructorNode != null) {
this.fieldsNode = PropertyNode.createGetHidden(context, null, JSFunction.CLASS_FIELDS_ID);
this.brandNode = PropertyNode.createGetHidden(context, null, JSFunction.PRIVATE_BRAND_ID);
}
}
public static JavaScriptNode create(JSContext context, JavaScriptNode targetNode, JavaScriptNode constructorNode) {
return InitializeInstanceElementsNodeGen.create(context, targetNode, constructorNode);
}
public static InitializeInstanceElementsNode create(JSContext context) {
return InitializeInstanceElementsNodeGen.create(context, null, null);
}
public final Object executeStaticFields(Object targetConstructor, Object[][] staticFields) {
return executeEvaluated(targetConstructor, Undefined.instance, staticFields, Undefined.instance);
}
protected abstract Object executeEvaluated(Object target, Object constructor, Object[][] fields, Object brand);
@ExplodeLoop(kind = LoopExplosionKind.FULL_UNROLL)
@Specialization
protected static Object withFields(Object target, Object constructor, Object[][] fields, Object brand,
@Cached("createBrandAddNode(brand, context)") @Shared("privateBrandAdd") PrivateFieldAddNode privateBrandAddNode,
@Cached("createFieldNodes(fields, context)") DefineFieldNode[] fieldNodes) {
privateBrandAdd(target, constructor, fields, brand, privateBrandAddNode);
int size = fieldNodes.length;
assert size == fields.length;
for (int i = 0; i < size; i++) {
Object[] field = fields[i];
Object key = field[0];
Object initializer = field[1];
fieldNodes[i].defineField(target, key, initializer);
}
return target;
}
@Specialization
protected static Object privateBrandAdd(Object target, Object constructor, @SuppressWarnings("unused") Object fields, Object brand,
@Cached("createBrandAddNode(brand, context)") @Shared("privateBrandAdd") PrivateFieldAddNode privateBrandAddNode) {
assert (privateBrandAddNode != null) == (brand != Undefined.instance);
if (privateBrandAddNode != null) {
privateBrandAddNode.execute(target, brand, constructor);
}
return target;
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return create(context, cloneUninitialized(targetNode, materializedTags), cloneUninitialized(constructorNode, materializedTags));
}
static PrivateFieldAddNode createBrandAddNode(Object brand, JSContext context) {
CompilerAsserts.neverPartOfCompilation();
if (brand != Undefined.instance) {
return PrivateFieldAddNode.create(context);
} else {
return null;
}
}
static DefineFieldNode[] createFieldNodes(Object[][] fields, JSContext context) {
CompilerAsserts.neverPartOfCompilation();
int size = fields.length;
DefineFieldNode[] fieldNodes = new DefineFieldNode[size];
for (int i = 0; i < size; i++) {
Object[] field = fields[i];
Object key = field[0];
Object initializer = field[1];
boolean isAnonymousFunctionDefinition = (boolean) field[2];
JavaScriptBaseNode writeNode;
if (key instanceof HiddenKey) {
writeNode = PrivateFieldAddNode.create(context);
} else {
writeNode = WriteElementNode.create(context, true, true);
}
JSFunctionCallNode callNode = null;
if (initializer != Undefined.instance) {
callNode = JSFunctionCallNode.createCall();
}
SetFunctionNameNode setFunctionNameNode = null;
if (isAnonymousFunctionDefinition) {
setFunctionNameNode = SetFunctionNameNode.create();
}
fieldNodes[i] = new DefineFieldNode(writeNode, callNode, setFunctionNameNode);
}
return fieldNodes;
}
static final class DefineFieldNode extends JavaScriptBaseNode {
@Child JavaScriptBaseNode writeNode;
@Child JSFunctionCallNode callNode;
@Child SetFunctionNameNode setFunctionNameNode;
DefineFieldNode(JavaScriptBaseNode writeNode, JSFunctionCallNode callNode, SetFunctionNameNode setFunctionNameNode) {
this.writeNode = writeNode;
this.callNode = callNode;
this.setFunctionNameNode = setFunctionNameNode;
}
void defineField(Object target, Object key, Object initializer) {
assert (callNode != null) == (initializer != Undefined.instance);
Object value = Undefined.instance;
if (callNode != null) {
value = callNode.executeCall(JSArguments.createZeroArg(target, initializer));
if (setFunctionNameNode != null) {
setFunctionNameNode.execute(value, key);
}
}
if (writeNode instanceof PrivateFieldAddNode) {
assert key instanceof HiddenKey : key;
((PrivateFieldAddNode) writeNode).execute(target, key, value);
} else {
assert JSRuntime.isPropertyKey(key) : key;
((WriteElementNode) writeNode).executeWithTargetAndIndexAndValue(target, key, value);
}
}
}
}