* Copyright (c) 2021-2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "emitter.h"
#include "ir/irnode.h"
#include "util/helpers.h"
#include "varbinder/scope.h"
#include "varbinder/variable.h"
#include "compiler/base/literals.h"
#include "compiler/core/codeGen.h"
#include "compiler/core/regSpiller.h"
#include "compiler/core/ETSemitter.h"
#include "compiler/debugger/debuginfoDumper.h"
#include "compiler/base/catchTable.h"
#include "es2panda.h"
#include "parser/program/program.h"
#include "checker/types/type.h"
#include "generated/isa.h"
#include "util/es2pandaMacros.h"
#include "public/public.h"
namespace ark::es2panda::compiler {
using LiteralPair = std::pair<pandasm::LiteralArray::Literal, pandasm::LiteralArray::Literal>;
static LiteralPair TransformMethodLiterals(const compiler::Literal *literal)
{
pandasm::LiteralArray::Literal valueLit;
pandasm::LiteralArray::Literal tagLit;
compiler::LiteralTag tag = literal->Tag();
switch (tag) {
case compiler::LiteralTag::METHOD: {
valueLit.tag = panda_file::LiteralTag::METHOD;
valueLit.value = literal->GetMethod();
break;
}
case compiler::LiteralTag::ASYNC_METHOD: {
valueLit.tag = panda_file::LiteralTag::ASYNCMETHOD;
valueLit.value = literal->GetMethod();
break;
}
case compiler::LiteralTag::GENERATOR_METHOD: {
valueLit.tag = panda_file::LiteralTag::GENERATORMETHOD;
valueLit.value = literal->GetMethod();
break;
}
case compiler::LiteralTag::ASYNC_GENERATOR_METHOD: {
valueLit.tag = panda_file::LiteralTag::ASYNCGENERATORMETHOD;
valueLit.value = literal->GetMethod();
break;
}
default: {
ES2PANDA_UNREACHABLE();
break;
}
}
tagLit.tag = panda_file::LiteralTag::TAGVALUE;
tagLit.value = static_cast<uint8_t>(valueLit.tag);
return {tagLit, valueLit};
}
static LiteralPair TransformLiteral(const compiler::Literal *literal)
{
pandasm::LiteralArray::Literal valueLit;
pandasm::LiteralArray::Literal tagLit;
compiler::LiteralTag tag = literal->Tag();
switch (tag) {
case compiler::LiteralTag::BOOLEAN: {
valueLit.tag = panda_file::LiteralTag::BOOL;
valueLit.value = literal->GetBoolean();
break;
}
case compiler::LiteralTag::INTEGER: {
valueLit.tag = panda_file::LiteralTag::INTEGER;
valueLit.value = literal->GetInteger();
break;
}
case compiler::LiteralTag::DOUBLE: {
valueLit.tag = panda_file::LiteralTag::DOUBLE;
valueLit.value = literal->GetDouble();
break;
}
case compiler::LiteralTag::STRING: {
valueLit.tag = panda_file::LiteralTag::STRING;
valueLit.value = literal->GetString();
break;
}
case compiler::LiteralTag::ACCESSOR: {
valueLit.tag = panda_file::LiteralTag::ACCESSOR;
valueLit.value = static_cast<uint8_t>(0);
break;
}
case compiler::LiteralTag::NULL_VALUE: {
valueLit.tag = panda_file::LiteralTag::NULLVALUE;
valueLit.value = static_cast<uint8_t>(0);
break;
}
default:
return TransformMethodLiterals(literal);
}
tagLit.tag = panda_file::LiteralTag::TAGVALUE;
tagLit.value = static_cast<uint8_t>(valueLit.tag);
return {tagLit, valueLit};
}
void FunctionEmitter::Generate()
{
auto *func = GenFunctionSignature();
if (func == nullptr) {
return;
}
GenFunctionInstructions(func);
GenVariablesDebugInfo(func);
GenSourceFileDebugInfo(func);
GenFunctionCatchTables(func);
GenFunctionAnnotations(func);
}
util::StringView FunctionEmitter::SourceCode() const
{
return cg_->VarBinder()->Program()->SourceCode();
}
void FunctionEmitter::GenInstructionDebugInfo(const IRNode *ins, pandasm::Ins *pandaIns)
{
constexpr auto INVALID_LINE_NO = static_cast<std::uint32_t>(-1);
const auto *rootNode = cg_->RootNode();
if (rootNode->IsScriptFunction() && rootNode->HasAstNodeFlags(ir::AstNodeFlags::NO_DEBUG_LINE_INFO)) {
pandaIns->insDebug.SetLineNumber(INVALID_LINE_NO);
return;
}
const ir::AstNode *astNode = ins->Node();
ES2PANDA_ASSERT(astNode != nullptr);
if (astNode == FIRST_NODE_OF_FUNCTION) {
astNode = cg_->Debuginfo().FirstStatement();
if (astNode == nullptr) {
return;
}
}
auto nodeRange = astNode->Range();
pandaIns->insDebug.SetLineNumber(nodeRange.start.line == 0 ? INVALID_LINE_NO : nodeRange.start.line + 1U);
}
void FunctionEmitter::GenFunctionInstructions(pandasm::Function *func)
{
const size_t insCount = cg_->Insns().size();
func->ins.resize(insCount);
uint32_t totalRegs = cg_->TotalRegsNum();
size_t index = 0;
for (const auto *ins : cg_->Insns()) {
auto &pandaIns = func->ins[index++];
ins->Transform(&pandaIns, programElement_, totalRegs);
GenInstructionDebugInfo(ins, &pandaIns);
}
}
void FunctionEmitter::GenFunctionAnnotations(pandasm::Function *func)
{
pandasm::AnnotationData funcAnnotationData("_ESAnnotation");
pandasm::AnnotationElement icSizeAnnotationElement(
"icSize",
std::make_unique<pandasm::ScalarValue>(pandasm::ScalarValue::Create<pandasm::Value::Type::U32>(cg_->IcSize())));
funcAnnotationData.AddElement(std::move(icSizeAnnotationElement));
pandasm::AnnotationElement parameterLengthAnnotationElement(
"parameterLength", std::make_unique<pandasm::ScalarValue>(
pandasm::ScalarValue::Create<pandasm::Value::Type::U32>(cg_->FormalParametersCount())));
funcAnnotationData.AddElement(std::move(parameterLengthAnnotationElement));
pandasm::AnnotationElement funcNameAnnotationElement(
"funcName", std::make_unique<pandasm::ScalarValue>(
pandasm::ScalarValue::Create<pandasm::Value::Type::STRING>(cg_->FunctionName().Mutf8())));
funcAnnotationData.AddElement(std::move(funcNameAnnotationElement));
func->metadata->AddAnnotations({funcAnnotationData});
}
void FunctionEmitter::GenFunctionCatchTables(pandasm::Function *func)
{
func->catchBlocks.reserve(cg_->CatchList().size());
for (const auto *catchBlock : cg_->CatchList()) {
const auto &labelSet = catchBlock->LabelSet();
auto &pandaCatchBlock = func->catchBlocks.emplace_back();
pandaCatchBlock.exceptionRecord = catchBlock->Exception();
pandaCatchBlock.tryBeginLabel = labelSet.TryBegin()->Id();
pandaCatchBlock.tryEndLabel = labelSet.TryEnd()->Id();
pandaCatchBlock.catchBeginLabel = labelSet.CatchBegin()->Id();
pandaCatchBlock.catchEndLabel = labelSet.CatchBegin()->Id();
}
}
static void GenLocalVariableInfo(pandasm::debuginfo::LocalVariable &variableDebug, varbinder::Variable *var,
std::tuple<uint32_t, uint32_t, uint32_t> info, ScriptExtension extension,
uint32_t spillOffset)
{
const auto [start, varsLength, totalRegsNum] = info;
variableDebug.name = var->Name().Mutf8();
if (extension == ScriptExtension::JS) {
variableDebug.signature = "any";
variableDebug.signatureType = "any";
} else {
ES2PANDA_ASSERT(var->AsLocalVariable()->TsType() != nullptr);
std::stringstream ss;
var->AsLocalVariable()->TsType()->ToDebugInfoType(ss);
variableDebug.signature = ss.str();
variableDebug.signatureType = ss.str();
}
uint32_t idx = var->AsLocalVariable()->Vreg().GetIndex();
if (spillOffset != 0) {
ES2PANDA_ASSERT(idx >= spillOffset);
idx -= spillOffset;
}
variableDebug.reg = static_cast<int32_t>(IRNode::MapRegister(idx, totalRegsNum));
variableDebug.start = start;
variableDebug.length = static_cast<uint32_t>(varsLength);
}
static bool IsVariableFirstDefByInsn(const IRNode *ins, const varbinder::Variable *var, uint32_t spillOffset)
{
ES2PANDA_ASSERT(ins != nullptr);
if (var == nullptr || !var->IsLocalVariable()) {
return false;
}
const uint32_t varReg = var->AsLocalVariable()->Vreg().GetIndex() - spillOffset;
std::array<const VReg *, IRNode::MAX_REG_OPERAND> regs {};
const size_t regCnt = ins->Registers(®s);
for (size_t idx = 0; idx < regCnt; ++idx) {
const auto kind = ins->GetOperandRegKind(idx);
if (kind != OperandKind::DST_VREG && kind != OperandKind::SRC_DST_VREG) {
continue;
}
if (regs[idx] != nullptr && regs[idx]->GetIndex() == varReg) {
return true;
}
}
return false;
}
void FunctionEmitter::GenScopeVariableInfoEnd(pandasm::Function *func, const varbinder::Scope *scope, uint32_t count,
uint32_t scopeStart, const VariablesStartsMap &starts) const
{
const auto extension = cg_->VarBinder()->Program()->Extension();
auto varsLength = static_cast<uint32_t>(count - scopeStart + 1);
if (scope->IsFunctionScope()) {
for (auto *param : scope->AsFunctionScope()->ParamScope()->Params()) {
auto &variableDebug = func->localVariableDebug.emplace_back();
GenLocalVariableInfo(variableDebug, param, std::make_tuple(scopeStart, varsLength, cg_->TotalRegsNum()),
extension, 0U);
}
}
const auto &unsortedBindings = scope->Bindings();
std::map<util::StringView, es2panda::varbinder::Variable *> bindings(unsortedBindings.begin(),
unsortedBindings.end());
for (const auto &[_, variable] : bindings) {
(void)_;
const auto *decl = variable->Declaration();
if (!variable->IsLocalVariable() || variable->LexicalBound() || decl->IsTypeAliasDecl() ||
decl->IsLabelDecl() || decl->IsClassDecl() || decl->IsInterfaceDecl()) {
continue;
}
if (decl->IsParameterDecl() && !scope->IsCatchParamScope()) {
continue;
}
uint32_t localStart = scopeStart;
uint32_t localLength = varsLength;
auto iter = starts.find(variable);
if (iter != starts.end()) {
localStart = iter->second;
localLength = static_cast<uint32_t>(count - localStart + 1);
}
auto &variableDebug = func->localVariableDebug.emplace_back();
GenLocalVariableInfo(variableDebug, variable, std::make_tuple(localStart, localLength, cg_->TotalRegsNum()),
extension, cg_->GetSpillDebugOffset());
}
}
void FunctionEmitter::GenScopeVariableInfo(pandasm::Function *func, const varbinder::Scope *scope) const
{
const auto &instructions = cg_->Insns();
auto lastIter = instructions.end();
uint32_t count = 0;
auto iter = instructions.begin();
for (; iter != lastIter; ++iter, ++count) {
if (*iter == cg_->ScopeInsnRange(scope).first) {
break;
}
}
if (iter == lastIter || *iter == cg_->ScopeInsnRange(scope).second) {
return;
}
uint32_t start = count;
auto checkNodeIsValid = [](const ir::AstNode *node) { return node != nullptr && node != FIRST_NODE_OF_FUNCTION; };
std::unordered_map<const varbinder::Variable *, uint32_t> varsStarts;
for (const auto *scopeEnd = cg_->ScopeInsnRange(scope).second; iter != lastIter && *iter != scopeEnd;
++iter, ++count) {
const auto *node = (*iter)->Node();
if (!checkNodeIsValid(node)) {
continue;
}
auto *parent = node->Parent();
if (!checkNodeIsValid(parent)) {
continue;
}
const varbinder::Variable *var = nullptr;
if (parent->IsVariableDeclarator()) {
var = parent->AsVariableDeclarator()->Id()->Variable();
int spillOffset = cg_->GetSpillDebugOffset();
if (!IsVariableFirstDefByInsn(*iter, var, spillOffset)) {
continue;
}
} else if (parent->IsCatchClause()) {
var = node->Variable();
}
if (var != nullptr && varsStarts.count(var) == 0) {
varsStarts.emplace(var, count);
}
}
ES2PANDA_ASSERT(iter != lastIter);
GenScopeVariableInfoEnd(func, scope, count, start, varsStarts);
}
void FunctionEmitter::GenVariablesDebugInfo(pandasm::Function *func)
{
if (!cg_->IsDebug()) {
return;
}
for (const auto *scope : cg_->Debuginfo().VariableDebugInfo()) {
GenScopeVariableInfo(func, scope);
}
}
Emitter::Emitter(const public_lib::Context *context) : context_(context)
{
prog_ = new pandasm::Program();
}
Emitter::~Emitter()
{
delete prog_;
}
static void UpdateLiteralBufferId([[maybe_unused]] ark::pandasm::Ins *ins, [[maybe_unused]] uint32_t offset)
{
#ifdef PANDA_WITH_ECMASCRIPT
switch (ins->opcode) {
case pandasm::Opcode::ECMA_DEFINECLASSWITHBUFFER: {
if (auto pos = ins->ImmSize(); pos > 0U) {
--pos;
ins->SetImm(pos, std::get<int64_t>(ins->GetImm(pos)) + offset);
}
break;
}
case pandasm::Opcode::ECMA_CREATEARRAYWITHBUFFER:
case pandasm::Opcode::ECMA_CREATEOBJECTWITHBUFFER:
case pandasm::Opcode::ECMA_CREATEOBJECTHAVINGMETHOD:
case pandasm::Opcode::ECMA_DEFINECLASSPRIVATEFIELDS: {
constexpr int BASE10 = 10;
if (auto pos = ins->IDSize(); pos > 0U) {
--pos;
auto storedOffset = std::strtoul(ins->GetID(pos).data(), nullptr, BASE10);
storedOffset += offset;
ins->SetID(pos, std::to_string(storedOffset));
}
break;
}
default: {
ES2PANDA_UNREACHABLE();
break;
}
}
#else
ES2PANDA_UNREACHABLE();
#endif
}
void Emitter::AddProgramElement(ProgramElement *programElement)
{
prog_->strings.insert(programElement->Strings().begin(), programElement->Strings().end());
uint32_t newLiteralBufferIndex = literalBufferIndex_;
for (const auto &buff : programElement->BuffStorage()) {
AddLiteralBuffer(buff, newLiteralBufferIndex++);
}
for (auto *ins : programElement->LiteralBufferIns()) {
UpdateLiteralBufferId(ins, literalBufferIndex_);
}
literalBufferIndex_ = newLiteralBufferIndex;
auto *function = programElement->Function();
prog_->AddToFunctionTable(std::move(*function));
}
static std::string CanonicalizeName(std::string name)
{
std::replace_if(
name.begin(), name.end(), [](char c) { return (c == '<' || c == '>' || c == '.' || c == ':' || c == ';'); },
'-');
name.append(std::to_string(0));
return name;
}
static std::string DumpAsmFunction(std::string name, const pandasm::Function &func)
{
std::stringstream ss;
ss << ".function any " << CanonicalizeName(std::move(name)) << '(';
for (uint32_t i = 0; i < func.GetParamsNum(); i++) {
ss << "any a" << std::to_string(i);
if (i != func.GetParamsNum() - 1) {
ss << ", ";
}
}
ss << ") {" << std::endl;
for (const auto &ins : func.ins) {
ss << (ins.HasLabel() ? "" : "\t") << ins.ToString("", true, func.GetTotalRegs()) << std::endl;
}
ss << "}" << std::endl << std::endl;
for (const auto &ct : func.catchBlocks) {
if (ct.exceptionRecord.empty()) {
ss << ".catchall ";
} else {
ss << ".catch " << ct.exceptionRecord << ", ";
}
ss << ct.tryBeginLabel << ", " << ct.tryEndLabel << ", " << ct.catchBeginLabel << std::endl << std::endl;
}
return ss.str();
}
void Emitter::DumpAsm(const pandasm::Program *prog)
{
auto &ss = std::cout;
ss << ".language ECMAScript" << std::endl << std::endl;
for (const auto &[name, func] : prog->functionStaticTable) {
ss << DumpAsmFunction(name, func);
}
for (const auto &[name, func] : prog->functionInstanceTable) {
ss << DumpAsmFunction(name, func);
}
ss << std::endl;
}
void Emitter::AddLiteralBuffer(const LiteralBuffer &literals, uint32_t index)
{
std::vector<pandasm::LiteralArray::Literal> literalArray;
for (const auto &literal : literals) {
auto [tagLit, valueLit] = TransformLiteral(&literal);
literalArray.emplace_back(tagLit);
literalArray.emplace_back(valueLit);
}
auto literalArrayInstance = pandasm::LiteralArray(std::move(literalArray));
prog_->literalarrayTable.emplace(std::to_string(index), std::move(literalArrayInstance));
}
pandasm::Program *Emitter::DumpDebugInfo()
{
if (Context()->config->options->IsDumpDebugInfo()) {
debuginfo::DebugInfoDumper dumper(prog_);
dumper.Dump();
}
if (context_->parserProgram->VarBinder()->IsGenStdLib()) {
auto it = prog_->recordTable.find(std::string(Signatures::ETS_GLOBAL));
if (it != prog_->recordTable.end()) {
prog_->recordTable.erase(it);
}
}
prog_->metadata = std::move(context_->metadata);
auto *prog = prog_;
prog_ = nullptr;
return prog;
}
}