/*############################################################################## Copyright (C) 2011 HPCC Systems. All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ############################################################################## */ #include "jliball.hpp" #include "hql.hpp" #include "platform.h" #include "jlib.hpp" #include "jhash.hpp" #include "jmisc.hpp" #include "jstream.ipp" #include "jdebug.hpp" #include "hql.hpp" #include "hqlattr.hpp" #include "hqlfold.hpp" #include "hqlthql.hpp" #include "hqltrans.ipp" #include "hqlutil.hpp" #include "hqlcpp.ipp" #include "hqlcatom.hpp" #include "hqlcpputil.hpp" #include "hqlgraph.ipp" #include "hqllib.ipp" #include "hqlwcpp.hpp" #include "hqlttcpp.ipp" #include "hqlinline.hpp" static IHqlExpression * queryLibraryInputSequence(IHqlExpression * expr) { IHqlExpression * arg = expr->queryChild(0); if (arg->isRecord()) arg = expr->queryChild(1); return arg; } HqlCppLibrary::HqlCppLibrary(HqlCppTranslator & _translator, IHqlExpression * libraryInterface, ClusterType _clusterType) : translator(_translator), inputMapper(libraryInterface), clusterType(_clusterType) { assertex(libraryInterface->getOperator() == no_funcdef); scopeExpr = libraryInterface->queryChild(0); assertex(scopeExpr->getOperator() == no_virtualscope); streamedCount = 0; if (clusterType != HThorCluster) streamedCount = inputMapper.numStreamedInputs(); extractOutputs(); } void HqlCppLibrary::extractOutputs() { HqlExprArray symbols; scopeExpr->queryScope()->getSymbols(symbols); IHqlScope * scope = scopeExpr->queryScope(); HqlDummyLookupContext dummyctx(NULL); ForEachItemIn(i, symbols) { IHqlExpression & cur = symbols.item(i); if (isExported(&cur)) { _ATOM name = cur.queryName(); OwnedHqlExpr value = scope->lookupSymbol(name, LSFpublic, dummyctx); if (value && !value->isFunction()) { if (value->isDataset() || value->isDatarow() || value->queryType()->isScalar()) { OwnedHqlExpr null = createNullExpr(value); outputs.append(*cur.cloneAllAnnotations(null)); } } } } outputs.sort(compareSymbolsByName); } unsigned HqlCppLibrary::getInterfaceHash() const { unsigned crc = getHash(outputs, 0x12345678); // only hashes type and name, not implementation crc = getHash(inputMapper.queryRealParameters(), crc); if (translator.queryOptions().implicitLinkedChildRows) crc ^= 0x456271; if (crc == 0) crc = 0x87654321; // ensure it is non zero. return crc; } unsigned HqlCppLibrary::getHash(const HqlExprArray & values, unsigned crc) const { unsigned num = values.ordinality(); crc = hashc((const byte *)&num, sizeof(num), crc); ForEachItemIn(i, values) { IHqlExpression & cur = values.item(i); //names are significant because inputs/outputs are ordered by name const char * name = cur.queryName()->str(); crc = hashnc((const byte *)name, strlen(name), crc); ITypeInfo * type = cur.queryType(); byte tc = type->getTypeCode(); switch (tc) { case type_record: case type_row: case type_table: case type_groupedtable: { OwnedHqlExpr normalizedRecord = normalizeRecord(translator, cur.queryRecord()); OwnedHqlExpr serialized = getSerializedForm(normalizedRecord); unsigned recordCrc = getExpressionCRC(serialized); crc = hashc((const byte *)&tc, sizeof(tc), crc); crc = hashc((const byte *)&recordCrc, sizeof(recordCrc), crc); break; } default: { size32_t size = type->getSize(); crc = hashc((const byte *)&tc, sizeof(tc), crc); crc = hashc((const byte *)&size, sizeof(size), crc); break; } } } return crc; } unsigned HqlCppLibrary::queryOutputIndex(_ATOM name) const { ForEachItemIn(i, outputs) { if (outputs.item(i).queryName() == name) return i; } return NotFound; } HqlCppLibraryImplementation::HqlCppLibraryImplementation(HqlCppTranslator & _translator, IHqlExpression * libraryInterface, IHqlExpression * _libraryId, ClusterType _clusterType) : HqlCppLibrary(_translator, libraryInterface, _clusterType), libraryId(_libraryId) { inputMapper.mapRealToLogical(inputExprs, logicalParams, libraryId, (clusterType != HThorCluster), translator.targetThor()); } HqlCppLibraryInstance::HqlCppLibraryInstance(HqlCppTranslator & translator, IHqlExpression * _instanceExpr, ClusterType clusterType) : instanceExpr(_instanceExpr) { assertex(instanceExpr->getOperator() == no_libraryscopeinstance); libraryFuncdef = instanceExpr->queryDefinition(); assertex(libraryFuncdef->getOperator() == no_funcdef); IHqlExpression * libraryScope = libraryFuncdef->queryChild(0); assertex(libraryScope->getOperator() == no_libraryscope); IHqlExpression * libraryInterface = queryPropertyChild(libraryScope, implementsAtom, 0); assertex(libraryInterface); library.setown(new HqlCppLibrary(translator, libraryInterface, clusterType)); assertex(library->totalInputs() == (libraryFuncdef->queryChild(1)->numChildren())); } //--------------------------------------------------------------------------- static HqlTransformerInfo hqlLibraryTransformerInfo("HqlLibraryTransformer"); class HqlLibraryTransformer: public QuickHqlTransformer { public: HqlLibraryTransformer(IConstWorkUnit * _wu, bool _isLibrary) : QuickHqlTransformer(hqlLibraryTransformerInfo, NULL), wu(_wu) { ignoreFirstScope = _isLibrary; } IHqlExpression * doTransformLibrarySelect(IHqlExpression * expr) { //Map the new attribute IHqlExpression * oldModule = expr->queryChild(1); OwnedHqlExpr newModule = transform(oldModule); if (oldModule == newModule) return LINK(expr); _ATOM attrName = expr->queryChild(3)->queryName(); HqlDummyLookupContext dummyctx(NULL); OwnedHqlExpr value = newModule->queryScope()->lookupSymbol(attrName, makeLookupFlags(true, expr->hasProperty(ignoreBaseAtom), false), dummyctx); assertex(value != NULL); IHqlExpression * oldAttr = expr->queryChild(2); if (oldAttr->isDataset() || oldAttr->isDatarow()) return value.getClear(); assertex(value->isDataset()); IHqlExpression * field = value->queryRecord()->queryChild(0); OwnedHqlExpr select = createRow(no_selectnth, value.getClear(), createConstantOne()); return createSelectExpr(select.getClear(), LINK(field)); // no newAtom because not normalised yet } virtual IHqlExpression * createTransformedBody(IHqlExpression * expr) { switch (expr->getOperator()) { case no_libraryselect: return doTransformLibrarySelect(expr); } return QuickHqlTransformer::createTransformedBody(expr); } protected: IHqlExpression * createSimplifiedLibrary(IHqlExpression * libraryExpr) { IHqlScope * oldScope = libraryExpr->queryScope(); IHqlScope * lookupScope = oldScope; IHqlExpression * interfaceExpr = queryPropertyChild(libraryExpr, implementsAtom, 0); if (interfaceExpr) lookupScope = interfaceExpr->queryScope(); HqlExprArray symbols, newSymbols; lookupScope->getSymbols(symbols); HqlDummyLookupContext dummyctx(NULL); ForEachItemIn(i, symbols) { IHqlExpression & cur = symbols.item(i); if (isExported(&cur)) { _ATOM name = cur.queryName(); OwnedHqlExpr oldSymbol = oldScope->lookupSymbol(name, LSFpublic, dummyctx); OwnedHqlExpr newValue; ITypeInfo * type = oldSymbol->queryType(); if (oldSymbol->isDataset() || oldSymbol->isDatarow()) { newValue.setown(createNullExpr(oldSymbol)); } else { //Convert a scalar to a select from a dataset OwnedHqlExpr field = createField(unknownAtom, LINK(type), NULL, NULL); OwnedHqlExpr newRecord = createRecord(field); OwnedHqlExpr ds = createDataset(no_null, LINK(newRecord)); newValue.setown(createNullExpr(ds)); } if (oldSymbol->isFunction()) { // Should have been caught in the parser.. Following code should be correct if we ever work out how to implement throwUnexpected(); HqlExprArray parms; unwindChildren(parms, oldSymbol, 1); IHqlExpression * formals = createSortList(parms); newValue.setown(createFunctionDefinition(name, newValue.getClear(), formals, NULL, NULL)); } IHqlExpression * newSym = oldSymbol->cloneAllAnnotations(newValue); newSymbols.append(*newSym); } } HqlExprArray children; unwindChildren(children, libraryExpr); return queryExpression(oldScope->clone(children, newSymbols)); } protected: IConstWorkUnit * wu; bool ignoreFirstScope; }; class HqlEmbeddedLibraryTransformer: public HqlLibraryTransformer { public: HqlEmbeddedLibraryTransformer(IConstWorkUnit * _wu, HqlExprArray & _internalLibraries, bool _isLibrary) : HqlLibraryTransformer(_wu, _isLibrary), internalLibraries(_internalLibraries) { } virtual IHqlExpression * createTransformedBody(IHqlExpression * expr) { switch (expr->getOperator()) { case no_libraryscopeinstance: { IHqlExpression * scopeFunc = expr->queryDefinition(); assertex(scopeFunc->getOperator() == no_funcdef); IHqlExpression * moduleExpr = scopeFunc->queryChild(0); assertex(moduleExpr->getOperator() == no_libraryscope); IHqlExpression * internalExpr = moduleExpr->queryProperty(internalAtom); if (internalExpr) { IHqlExpression * nameExpr = moduleExpr->queryProperty(nameAtom); if (!matchedInternalLibraries.contains(*nameExpr)) { internalLibraries.append(*transformEmbeddedLibrary(scopeFunc)); matchedInternalLibraries.append(*LINK(nameExpr)); } //remove the parameter from the internal attribute from the module that is being called. //so save in subsequent translation OwnedHqlExpr newModuleExpr = replaceOwnedProperty(moduleExpr, createAttribute(internalAtom)); OwnedHqlExpr newScopeFunc = replaceChild(scopeFunc, 0, newModuleExpr); HqlExprArray instanceArgs; unwindChildren(instanceArgs, expr); return createLibraryInstance(LINK(newScopeFunc), instanceArgs); } else return LINK(expr); //otherwise already transformed.... break; } case no_libraryscope: if (!ignoreFirstScope) { //Now create a simplified no_library scope with null values for each of the values of the exported symbols //Don't walk contents because that is an implementation detail, which could change return createSimplifiedLibrary(expr); } ignoreFirstScope = false; break; } return HqlLibraryTransformer::createTransformedBody(expr); } IHqlExpression * transformEmbeddedLibrary(IHqlExpression * expr) { //avoid special casing above assertex(expr->getOperator() == no_funcdef); IHqlExpression * library = expr->queryChild(0); HqlExprArray args; args.append(*QuickHqlTransformer::createTransformedBody(library)); unwindChildren(args, expr, 1); return expr->clone(args); } protected: HqlExprArray & internalLibraries; HqlExprArray matchedInternalLibraries; }; //--------------------------------------------------------------------------- void HqlCppTranslator::processEmbeddedLibraries(HqlExprArray & exprs, HqlExprArray & internalLibraries, bool isLibrary) { HqlExprArray transformed; HqlEmbeddedLibraryTransformer transformer(wu(), internalLibraries, isLibrary); ForEachItemIn(i, exprs) transformed.append(*transformer.transform(&exprs.item(i))); replaceArray(exprs, transformed); } //--------------------------------------------------------------------------- static IHqlExpression * splitSelect(IHqlExpression * & rootDataset, IHqlExpression * expr, IHqlExpression * selSeq) { assertex(expr->getOperator() == no_select); IHqlExpression * ds = expr->queryChild(0); IHqlExpression * field = expr->queryChild(1); IHqlExpression * attrSelector; if (ds->isDatarow() && (ds->getOperator() == no_select)) { attrSelector = splitSelect(rootDataset, ds, selSeq); } else { rootDataset = ds; attrSelector = createSelector(no_left, ds, selSeq); } return createSelectExpr(attrSelector, LINK(field)); } static IHqlExpression * convertScalarToDataset(IHqlExpression * expr) { switch (expr->getOperator()) { case NO_AGGREGATE: return convertScalarAggregateToDataset(expr); case no_select: { //Project dataset down to a single field... IHqlExpression * ds; OwnedHqlExpr selSeq = createSelectorSequence(); OwnedHqlExpr newExpr = splitSelect(ds, expr, selSeq); IHqlExpression * field = expr->queryChild(1); OwnedHqlExpr record = createRecord(field); OwnedHqlExpr assign = createAssign(createSelectExpr(createSelector(no_self, record, NULL), LINK(field)), LINK(newExpr)); OwnedHqlExpr row = createRow(no_projectrow, LINK(ds), createComma(createValue(no_transform, makeTransformType(record->getType()), LINK(assign)), LINK(selSeq))); return LINK(row); //Following is more strictly correct, but messes up the resourcing. //return createDatasetFromRow(LINK(row)); } break; } OwnedHqlExpr field = createField(valueAtom, expr->getType(), NULL, NULL); OwnedHqlExpr record = createRecord(field); OwnedHqlExpr assign = createAssign(createSelectExpr(createSelector(no_self, record, NULL), LINK(field)), LINK(expr)); OwnedHqlExpr row = createRow(no_createrow, createValue(no_transform, makeTransformType(record->getType()), LINK(assign))); return createDatasetFromRow(LINK(row)); } void HqlCppLibraryImplementation::mapLogicalToImplementation(HqlExprArray & exprs, IHqlExpression * libraryExpr) { //First replace parameters with streamed inputs, and no_libraryinputs, by creating some psuedo-arguments, //and then binding them. HqlExprArray actuals; appendArray(actuals, logicalParams); OwnedHqlExpr bound = createBoundFunction(NULL, libraryExpr, actuals, NULL, true); IHqlScope * scope = bound->queryScope(); assertex(scope); //Now resolve each of the outputs in the transformed module HqlDummyLookupContext dummyctx(NULL); unsigned numInputs = numStreamedInputs(); ForEachItemIn(i, outputs) { IHqlExpression & curOutput = outputs.item(i); OwnedHqlExpr output = scope->lookupSymbol(curOutput.queryName(), LSFpublic, dummyctx); // Do a global replace of input(n) with no_getgraphresult(n), and no_param with no_ if (!output->isDatarow() && !output->isDataset()) output.setown(convertScalarToDataset(output)); HqlExprArray args; args.append(*LINK(output)); args.append(*LINK(libraryId)); args.append(*getSizetConstant(i+numInputs)); exprs.append(*createValue(no_setgraphresult, makeVoidType(), args)); } } //--------------------------------------------------------------------------- void ThorBoundLibraryActivity::noteOutputUsed(_ATOM name) { unsigned matchIndex = libraryInstance->library->queryOutputIndex(name); assertex(matchIndex != NotFound); addGraphAttributeInt(graphNode, "_outputUsed", matchIndex); } ABoundActivity * HqlCppTranslator::doBuildActivityLibrarySelect(BuildCtx & ctx, IHqlExpression * expr) { IHqlExpression * module = expr->queryChild(1); ThorBoundLibraryActivity * callInstance = static_cast(buildCachedActivity(ctx, module)); callInstance->noteOutputUsed(expr->queryChild(3)->queryName()); return callInstance; } //--------------------------------------------------------------------------- void HqlCppTranslator::buildLibraryInstanceExtract(BuildCtx & ctx, HqlCppLibraryInstance * libraryInstance) { BuildCtx subctx(ctx); subctx.addQuotedCompound("virtual void createParentExtract(rtlRowBuilder & builder)"); BuildCtx beforeBuilderCtx(subctx); beforeBuilderCtx.addGroup(); Owned extractBuilder = createExtractBuilder(subctx, PETlibrary, NULL, GraphNonLocal, false); StringBuffer s; s.append("rtlRowBuilder & "); generateExprCpp(s, extractBuilder->queryExtractName()); s.append(" = builder;"); beforeBuilderCtx.addQuoted(s); beginExtract(subctx, extractBuilder); //Ensure all the values are added to the serialization in the correct order CHqlBoundExpr dummyTarget; unsigned numParams = libraryInstance->numParameters(); for (unsigned i2 = libraryInstance->numStreamedInputs(); i2 < numParams; i2++) { IHqlExpression * parameter = libraryInstance->queryParameter(i2); extractBuilder->addSerializedExpression(libraryInstance->queryActual(i2), parameter->queryType()); } endExtract(subctx, extractBuilder); } ABoundActivity * HqlCppTranslator::doBuildActivityLibraryInstance(BuildCtx & ctx, IHqlExpression * expr) { Owned libraryInstance = new HqlCppLibraryInstance(*this, expr, targetClusterType); CIArray boundInputs; unsigned numStreamed = libraryInstance->numStreamedInputs(); for (unsigned i1=0; i1 < numStreamed; i1++) boundInputs.append(*buildCachedActivity(ctx, libraryInstance->queryActual(i1))); IHqlExpression * moduleFunction = expr->queryDefinition(); // no_funcdef IHqlExpression * module = moduleFunction->queryChild(0); assertex(module->getOperator() == no_libraryscope); IHqlExpression * nameAttr = module->queryProperty(nameAtom); OwnedHqlExpr name = foldHqlExpression(nameAttr->queryChild(0)); IValue * nameValue = name->queryValue(); IHqlExpression * originalName = queryPropertyChild(module, _original_Atom, 0); StringBuffer libraryName; if (nameValue) nameValue->getStringValue(libraryName); Owned instance = new ActivityInstance(*this, ctx, TAKlibrarycall, expr, "LibraryCall"); StringBuffer graphLabel; if (originalName) { StringBuffer temp; temp.append(originalName->queryName()).toLowerCase(); graphLabel.append("Library").newline().append(temp); } else if (nameValue) graphLabel.append("Library").newline().append(libraryName); if (graphLabel.length()) instance->graphLabel.set(graphLabel.str()); buildActivityFramework(instance); buildInstancePrefix(instance); buildLibraryInstanceExtract(instance->startctx, libraryInstance); //MORE: Need to call functions to add the extract const HqlCppLibrary * library = libraryInstance->library; if (nameValue) { instance->addAttribute("libname", libraryName.str()); Owned wulib = wu()->updateLibraryByName(libraryName.str()); wulib->addActivity((unsigned)instance->activityId); } instance->addAttributeInt("_interfaceHash", library->getInterfaceHash()); instance->addAttributeBool("embedded", module->hasProperty(internalAtom)); instance->addAttributeInt("_maxOutputs", library->outputs.ordinality()); if (!targetHThor()) instance->addAttributeInt("_graphid", nextActivityId()); // reserve an id... // A debugging option to make it clearer how a library is being called. if (options.addLibraryInputsToGraph) { unsigned numParams = libraryInstance->numParameters(); for (unsigned iIn = 0; iIn < numParams; iIn++) { IHqlExpression * parameter = libraryInstance->queryParameter(iIn); StringBuffer paramName; StringBuffer paramEcl; paramName.append("input").append(iIn).append("__").append(parameter->queryName()); toECL(libraryInstance->queryActual(iIn), paramEcl, false, true); instance->addAttribute(paramName, paramEcl); } } StringBuffer s; BuildCtx metactx(instance->classctx); metactx.addQuotedCompound("virtual IOutputMetaData * queryOutputMeta(unsigned whichOutput)"); BuildCtx switchctx(metactx); switchctx.addQuotedCompound("switch (whichOutput)"); HqlDummyLookupContext dummyCtx(NULL); IHqlScope * moduleScope = module->queryScope(); ForEachItemIn(iout, library->outputs) { IHqlExpression & cur = library->outputs.item(iout); OwnedHqlExpr dataset = moduleScope->lookupSymbol(cur.queryName(), LSFpublic, dummyCtx); assertex(dataset && dataset->queryRecord()); MetaInstance meta(*this, dataset); buildMetaInfo(meta); switchctx.addQuoted(s.clear().append("case ").append(iout).append(": return &").append(meta.queryInstanceObject()).append(";")); } metactx.addReturn(queryQuotedNullExpr()); //Library Name must be onCreate invariant BuildCtx namectx(instance->createctx); namectx.addQuotedCompound("virtual char * getLibraryName()"); buildReturn(namectx, name, unknownVarStringType); buildInstanceSuffix(instance); ForEachItemIn(idx2, boundInputs) buildConnectInputOutput(ctx, instance, (ABoundActivity *)&boundInputs.item(idx2), 0, idx2); Owned boundInstance = instance->getBoundActivity(); return new ThorBoundLibraryActivity(boundInstance, instance->graphNode, libraryInstance); } void HqlCppTranslator::buildLibraryGraph(BuildCtx & ctx, IHqlExpression * expr, const char * graphName) { OwnedHqlExpr resourced = getResourcedGraph(expr->queryChild(0), NULL); beginGraph(ctx, graphName); traceExpression("beforeGenerate", resourced); BuildCtx initctx(ctx); initctx.addGroup(); initctx.addFilter(queryBoolExpr(false)); Owned libraryContext = new LibraryEvalContext(*this); initctx.associate(*libraryContext); unsigned numParams = outputLibrary->totalInputs(); for (unsigned i1 = outputLibrary->numStreamedInputs(); i1 < numParams; i1++) { IHqlExpression & parameter = *outputLibrary->queryInputExpr(i1); libraryContext->associateExpression(initctx, ¶meter); } Owned extractBuilder = createExtractBuilder(initctx, PETlibrary, outputLibraryId, GraphRemote, false); beginExtract(initctx, extractBuilder); BuildCtx evalctx(ctx); evalctx.addGroup(); evalctx.addFilter(queryBoolExpr(false)); //Ensure all the values are added to the serialization in the correct order by creating a dummy context and then //evaluating each parameter in term. Slightly ugly - it would be better using different calls. extractBuilder->associateCursors(evalctx, evalctx, GraphNonLocal); CHqlBoundExpr dummyTarget; for (unsigned i2 = outputLibrary->numStreamedInputs(); i2 < numParams; i2++) { IHqlExpression * parameter = outputLibrary->queryInputExpr(i2); extractBuilder->evaluateExpression(evalctx, parameter, dummyTarget, NULL, false); } BuildCtx graphctx(initctx); activeGraphCtx = &ctx; doBuildThorSubGraph(graphctx, resourced, SubGraphRoot, (unsigned)getIntValue(outputLibraryId->queryChild(0)), outputLibraryId); activeGraphCtx = NULL; endExtract(initctx, extractBuilder); endGraph(); } //--------------------------------------------------------------------------- LibraryEvalContext::LibraryEvalContext(HqlCppTranslator & _translator) : EvalContext(_translator, NULL, NULL) { } AliasKind LibraryEvalContext::evaluateExpression(BuildCtx & ctx, IHqlExpression * value, CHqlBoundExpr & tgt, bool evaluateLocally) { if (value->getOperator() == no_libraryinput) { IHqlExpression * seq = queryLibraryInputSequence(value); unsigned match = values.find(*seq); assertex(match != NotFound); tgt.setFromTranslated(&bound.item(match)); } else translator.buildTempExpr(ctx, value, tgt); return RuntimeAlias; } void LibraryEvalContext::associateExpression(BuildCtx & ctx, IHqlExpression * value) { //Add the association by sequence number, because this is pre record normalization. assertex(value->getOperator() == no_libraryinput); IHqlExpression * seq = queryLibraryInputSequence(value); assertex(!values.contains(*seq)); CHqlBoundTarget tempTarget; translator.createTempFor(ctx, value, tempTarget); values.append(*LINK(seq)); CHqlBoundExpr tgt; tgt.setFromTarget(tempTarget); bound.append(*tgt.getTranslatedExpr()); } void LibraryEvalContext::tempCompatiablityEnsureSerialized(const CHqlBoundTarget & tgt) { throwUnexpected(); }