use of com.google.template.soy.jbcsrc.restricted.CodeBuilder in project closure-templates by google.
the class SoyNodeCompiler method visitForNode.
@Override
protected Statement visitForNode(ForNode node) {
ForNonemptyNode nonEmptyNode = (ForNonemptyNode) node.getChild(0);
Optional<RangeArgs> exprAsRangeArgs = RangeArgs.createFromNode(node);
Scope scope = variables.enterScope();
final Variable indexVar;
final List<Statement> initializers = new ArrayList<>();
final Variable sizeVar;
final Variable itemVar;
if (exprAsRangeArgs.isPresent()) {
final CompiledForeachRangeArgs compiledArgs = calculateRangeArgs(node, scope);
initializers.addAll(compiledArgs.initStatements());
// The size is just the number of items in the range. The logic is a little tricky so we
// implement it in a runtime function: JbcsrcRuntime.rangeLoopLength
sizeVar = scope.createSynthetic(SyntheticVarName.foreachLoopLength(nonEmptyNode), MethodRef.RUNTIME_RANGE_LOOP_LENGTH.invoke(compiledArgs.start(), compiledArgs.end(), compiledArgs.step()), DERIVED);
indexVar = scope.createSynthetic(SyntheticVarName.foreachLoopIndex(nonEmptyNode), constant(0), STORE);
itemVar = scope.create(nonEmptyNode.getVarName(), new Expression(Type.LONG_TYPE, Feature.CHEAP) {
@Override
protected void doGen(CodeBuilder adapter) {
// executes ((long) start + index * step)
compiledArgs.start().gen(adapter);
compiledArgs.step().gen(adapter);
indexVar.local().gen(adapter);
adapter.visitInsn(Opcodes.IMUL);
adapter.visitInsn(Opcodes.IADD);
adapter.cast(Type.INT_TYPE, Type.LONG_TYPE);
}
}, DERIVED);
} else {
SoyExpression expr = exprCompiler.compile(node.getExpr()).unboxAs(List.class);
Variable listVar = scope.createSynthetic(SyntheticVarName.foreachLoopList(nonEmptyNode), expr, STORE);
initializers.add(listVar.initializer());
sizeVar = scope.createSynthetic(SyntheticVarName.foreachLoopLength(nonEmptyNode), MethodRef.LIST_SIZE.invoke(listVar.local()), DERIVED);
indexVar = scope.createSynthetic(SyntheticVarName.foreachLoopIndex(nonEmptyNode), constant(0), STORE);
itemVar = scope.create(nonEmptyNode.getVarName(), MethodRef.LIST_GET.invoke(listVar.local(), indexVar.local()).checkedCast(SOY_VALUE_PROVIDER_TYPE), DERIVED);
}
initializers.add(sizeVar.initializer());
final Statement loopBody = visitChildrenInNewScope(nonEmptyNode);
final Statement exitScope = scope.exitScope();
// it important for this to be generated after exitScope is called (or before enterScope)
final Statement emptyBlock = node.numChildren() == 2 ? visitChildrenInNewScope(node.getChild(1)) : null;
return new Statement() {
@Override
protected void doGen(CodeBuilder adapter) {
for (Statement initializer : initializers) {
initializer.gen(adapter);
}
sizeVar.local().gen(adapter);
Label emptyListLabel = new Label();
adapter.ifZCmp(Opcodes.IFEQ, emptyListLabel);
indexVar.initializer().gen(adapter);
Label loopStart = adapter.mark();
itemVar.initializer().gen(adapter);
loopBody.gen(adapter);
// index++
adapter.iinc(indexVar.local().index(), 1);
indexVar.local().gen(adapter);
sizeVar.local().gen(adapter);
// if index < list.size(), goto loopstart
adapter.ifICmp(Opcodes.IFLT, loopStart);
// exit the loop
exitScope.gen(adapter);
if (emptyBlock != null) {
Label skipIfEmptyBlock = new Label();
adapter.goTo(skipIfEmptyBlock);
adapter.mark(emptyListLabel);
emptyBlock.gen(adapter);
adapter.mark(skipIfEmptyBlock);
} else {
adapter.mark(emptyListLabel);
}
}
};
}
use of com.google.template.soy.jbcsrc.restricted.CodeBuilder in project closure-templates by google.
the class SoyNodeCompiler method visitVeLogNode.
@Override
protected Statement visitVeLogNode(VeLogNode node) {
final Label restartPoint = new Label();
final Expression hasLogger = parameterLookup.getRenderContext().hasLogger();
final boolean hasLogonlyExpression = node.getLogonlyExpression() != null;
final Expression logonlyExpression = hasLogonlyExpression ? exprCompiler.compile(node.getLogonlyExpression(), restartPoint).unboxAs(boolean.class) : BytecodeUtils.constant(false);
final Statement enterStatement = appendableExpression.enterLoggableElement(MethodRef.LOG_STATEMENT_CREATE.invoke(BytecodeUtils.constant(node.getLoggingId()), node.getConfigExpression() == null ? BytecodeUtils.constantNull(BytecodeUtils.MESSAGE_TYPE) : exprCompiler.compile(node.getConfigExpression(), restartPoint).unboxAs(Message.class), logonlyExpression)).toStatement();
final Statement body = Statement.concat(visitChildren(node));
final Statement exitStatement = appendableExpression.exitLoggableElement().toStatement();
return new Statement() {
@Override
protected void doGen(CodeBuilder cb) {
Label noLogger = new Label();
hasLogger.gen(cb);
cb.ifZCmp(EQ, noLogger);
enterStatement.gen(cb);
if (hasLogonlyExpression) {
Label bodyLabel = new Label();
cb.goTo(bodyLabel);
cb.mark(noLogger);
// if we get here then we have a logonly expression and no logger.
logonlyExpression.gen(cb);
cb.ifZCmp(EQ, bodyLabel);
cb.throwException(BytecodeUtils.ILLEGAL_STATE_EXCEPTION_TYPE, "Cannot set logonly=\"true\" unless there is a logger configured");
cb.mark(bodyLabel);
} else {
cb.mark(noLogger);
}
body.gen(cb);
Label end = new Label();
hasLogger.gen(cb);
cb.ifZCmp(EQ, end);
exitStatement.gen(cb);
cb.mark(end);
}
}.labelStart(restartPoint);
}
use of com.google.template.soy.jbcsrc.restricted.CodeBuilder in project closure-templates by google.
the class DetachState method generateReattachTable.
/**
* Returns a statement that generates the reattach jump table.
*
* <p>Note: This statement should be the <em>first</em> statement in any detachable method.
*/
Statement generateReattachTable() {
final Expression readField = stateField.accessor(thisExpr);
final Statement defaultCase = Statement.throwExpression(MethodRef.RUNTIME_UNEXPECTED_STATE_ERROR.invoke(readField));
return new Statement() {
@Override
protected void doGen(final CodeBuilder adapter) {
int[] keys = new int[reattaches.size()];
for (int i = 0; i < keys.length; i++) {
keys[i] = i;
}
readField.gen(adapter);
// Generate a switch table. Note, while it might be preferable to just 'goto state', Java
// doesn't allow computable gotos (probably because it makes verification impossible). So
// instead we emulate that with a jump table. And anyway we still need to execute 'restore'
// logic to repopulate the local variable tables, so the 'case' statements are a natural
// place for that logic to live.
adapter.tableSwitch(keys, new TableSwitchGenerator() {
@Override
public void generateCase(int key, Label end) {
if (key == 0) {
// State 0 is special, it means initial state, so we just jump to the very end
adapter.goTo(end);
return;
}
ReattachState reattachState = reattaches.get(key);
// restore and jump!
reattachState.restoreStatement().gen(adapter);
adapter.goTo(reattachState.reattachPoint());
}
@Override
public void generateDefault() {
defaultCase.gen(adapter);
}
}, // there are no 'holes' meaning that it is compact in the bytecode.
true);
}
};
}
use of com.google.template.soy.jbcsrc.restricted.CodeBuilder in project closure-templates by google.
the class DetachState method detachLimited.
/**
* Returns a Statement that will conditionally detach if the given {@link AdvisingAppendable} has
* been {@link AdvisingAppendable#softLimitReached() output limited}.
*/
Statement detachLimited(AppendableExpression appendable) {
if (!appendable.supportsSoftLimiting()) {
return appendable.toStatement();
}
final Label reattachPoint = new Label();
final SaveRestoreState saveRestoreState = variables.saveRestoreState();
Statement restore = saveRestoreState.restore();
int state = addState(reattachPoint, restore);
final Expression isSoftLimited = appendable.softLimitReached();
final Statement returnLimited = returnExpression(MethodRef.RENDER_RESULT_LIMITED.invoke());
final Statement saveState = stateField.putInstanceField(thisExpr, BytecodeUtils.constant(state));
return new Statement() {
@Override
protected void doGen(CodeBuilder adapter) {
isSoftLimited.gen(adapter);
// if !softLimited
adapter.ifZCmp(Opcodes.IFEQ, reattachPoint);
// ok we were limited, save state and return
// save locals
saveRestoreState.save().gen(adapter);
// save the state field
saveState.gen(adapter);
returnLimited.gen(adapter);
// Note, the reattach point for 'limited' is _after_ the check. That means we do not
// recheck the limit state. So if a caller calls us back without freeing any buffer we
// will print more before checking again. This is fine, because our caller is breaking the
// contract.
adapter.mark(reattachPoint);
}
};
}
use of com.google.template.soy.jbcsrc.restricted.CodeBuilder in project closure-templates by google.
the class DetachState method detachForRender.
/**
* Generate detach logic for calls.
*
* <p>Calls are a little different due to a desire to minimize the cost of detaches. We assume
* that if a given call site detaches once, it is more likely to detach multiple times. So we
* generate code that looks like:
*
* <pre>{@code
* RenderResult initialResult = template.render(appendable, renderContext);
* if (!initialResult.isDone()) {
* // save all fields
* state = REATTACH_RENDER;
* return initialResult;
* } else {
* goto END;
* }
* REATTACH_RENDER:
* // restore nothing!
* RenderResult secondResult = template.render(appendable, renderContext);
* if (!secondResult.isDone()) {
* // saveFields
* state = REATTACH_RENDER;
* return secondResult;
* } else {
* // restore all fields
* goto END;
* }
* END:
* }</pre>
*
* <p>With this technique we save re-running the save-restore logic for multiple detaches from the
* same call site. This should be especially useful for top level templates.
*
* @param callRender an Expression that can generate code to call the render method, should be
* safe to generate more than once.
*/
Statement detachForRender(final Expression callRender) {
checkArgument(callRender.resultType().equals(RENDER_RESULT_TYPE));
final Label reattachRender = new Label();
final SaveRestoreState saveRestoreState = variables.saveRestoreState();
// We pass NULL statement for the restore logic since we handle that ourselves below
int state = addState(reattachRender, Statement.NULL_STATEMENT);
final Statement saveState = stateField.putInstanceField(thisExpr, BytecodeUtils.constant(state));
return new Statement() {
@Override
protected void doGen(CodeBuilder adapter) {
// Legend: RR = RenderResult, Z = boolean
// Stack: RR
callRender.gen(adapter);
// Stack: RR, RR
adapter.dup();
// Stack: RR, Z
MethodRef.RENDER_RESULT_IS_DONE.invokeUnchecked(adapter);
// if isDone goto Done
Label end = new Label();
// Stack: RR
adapter.ifZCmp(Opcodes.IFNE, end);
saveRestoreState.save().gen(adapter);
saveState.gen(adapter);
adapter.returnValue();
adapter.mark(reattachRender);
// Stack: RR
callRender.gen(adapter);
// Stack: RR, RR
adapter.dup();
// Stack: RR, Z
MethodRef.RENDER_RESULT_IS_DONE.invokeUnchecked(adapter);
// if isDone goto restore
Label restore = new Label();
// Stack: RR
adapter.ifZCmp(Opcodes.IFNE, restore);
// no need to save or restore anything
adapter.returnValue();
// Stack: RR
adapter.mark(restore);
saveRestoreState.restore().gen(adapter);
// Stack: RR
adapter.mark(end);
// Stack:
adapter.pop();
}
};
}
Aggregations