From 8db571cfc84bd8354acc6b95c1278145a8991809 Mon Sep 17 00:00:00 2001 From: Georgy Bronnikov Date: Mon, 2 Oct 2023 17:36:57 +0300 Subject: [PATCH 1/3] Public interface library for es2panda Fixes internal #13898 Signed-off-by: Georgy Bronnikov --- BUILD.gn | 47 ++++ CMakeLists.txt | 40 ++- aot/BUILD.gn | 10 +- aot/CMakeLists.txt | 7 +- aot/main.cpp | 81 +----- checker/ETSchecker.h | 2 +- parser/ETSparser.h | 2 +- public/es2panda_lib.cpp | 365 +++++++++++++++++++++++++++ public/es2panda_lib.h | 50 ++++ test/CMakeLists.txt | 10 + test/public/es2panda_public_test.cpp | 62 +++++ util/generateBin.cpp | 90 +++++++ util/generateBin.h | 30 +++ {aot => util}/options.cpp | 4 +- {aot => util}/options.h | 10 +- 15 files changed, 708 insertions(+), 102 deletions(-) create mode 100644 public/es2panda_lib.cpp create mode 100644 public/es2panda_lib.h create mode 100644 test/public/es2panda_public_test.cpp create mode 100644 util/generateBin.cpp create mode 100644 util/generateBin.h rename {aot => util}/options.cpp (99%) rename {aot => util}/options.h (96%) diff --git a/BUILD.gn b/BUILD.gn index c9820ca3f..2db562256 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -346,6 +346,14 @@ libes2panda_sources = [ "util/ustring.cpp", ] +# libes2panda does not include bytecode optimizer, because it is used in +# libarkruntime, and conflict with JIT setup ensues +libes2panda_public_sources = [ + "public/es2panda_lib.cpp", + "util/generateBin.cpp", + "util/options.cpp", +] + build_gen_root = rebase_path(root_gen_dir) config("libes2panda_config") { @@ -367,6 +375,19 @@ libes2panda_configs = [ "$ark_root/libpandafile:arkfile_public_config", ] +libes2panda_public_configs = [ + sdk_libc_secshared_config, + "$ark_root:ark_config", + ":libes2panda_public_config", + ":libes2panda_config", + "$ark_root/libpandabase:arkbase_public_config", + "$ark_root/assembler:arkassembler_public_config", + "$ark_root/libpandafile:arkfile_public_config", + "$ark_root/bytecode_optimizer:bytecodeopt_public_config", + "$ark_root/compiler:arkcompiler_public_config", + "$ark_root/runtime:arkruntime_public_config", +] + ohos_shared_library("libes2panda") { sources = libes2panda_sources @@ -406,6 +427,32 @@ ohos_static_library("libes2panda_frontend_static") { ] } +ohos_shared_library("libes2panda_public") { + sources = libes2panda_public_sources + + configs = libes2panda_public_configs + + deps = [ + ":libes2panda", + "$ark_root/bytecode_optimizer:libarkbytecodeopt", + ] + + relative_install_dir = "ark" + output_extension = "so" + subsystem_name = "$ark_subsystem_name" +} + +ohos_static_library("libes2panda_public_frontend_static") { + sources = libes2panda_public_sources + + configs = libes2panda_public_configs + + deps = [ + ":libes2panda_frontend_static", + "$ark_root/bytecode_optimizer:libarkbytecodeopt_frontend_static", + ] +} + ark_isa_gen("isa_gen_es2panda") { template_files = [ "isa.h.erb", diff --git a/CMakeLists.txt b/CMakeLists.txt index e1ab2b797..5877b2f10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,10 +423,6 @@ panda_target_compile_options(es2panda-lib PRIVATE -fexceptions -Werror=shadow ) -panda_target_compile_definitions(es2panda-lib - PRIVATE "DEFAULT_ARKTSCONFIG=\"${GENERATED_DIR}/arktsconfig.json\"" -) - panda_target_link_libraries(es2panda-lib PUBLIC arkbase hmicuuc.z PRIVATE arkassembler @@ -444,7 +440,41 @@ if (PANDA_FUZZILLI) PRIVATE -fPIC ) endif() -panda_add_sanitizers(TARGET es2panda-lib SANITIZERS ${PANDA_SANITIZERS_LIST}) +panda_add_sanitizers(TARGET es2panda-lib SANITIZERS + ${PANDA_SANITIZERS_LIST}) + +# libes2panda does not include bytecode optimizer, because it is used in +# libarkruntime, and conflict with JIT setup ensues +set(ES2PANDA_PUBLIC_SOURCES + public/es2panda_lib.cpp + util/generateBin.cpp + util/options.cpp +) + + +panda_add_library(es2panda-public ${PANDA_DEFAULT_LIB_TYPE} ${ES2PANDA_PUBLIC_SOURCES}) +add_dependencies(es2panda-public es2panda-lib) + +panda_target_include_directories(es2panda-public + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${OUTPUT_DIR} +) + +panda_target_compile_definitions(es2panda-public + PRIVATE "DEFAULT_ARKTSCONFIG=\"${GENERATED_DIR}/arktsconfig.json\"" +) + +panda_target_compile_options(es2panda-public + PRIVATE -fexceptions -Werror=shadow +) + +panda_target_link_libraries(es2panda-public + PUBLIC arkbase es2panda-lib + PRIVATE arkbytecodeopt +) + +panda_add_sanitizers(TARGET es2panda-public SANITIZERS + ${PANDA_SANITIZERS_LIST}) add_subdirectory(aot) diff --git a/aot/BUILD.gn b/aot/BUILD.gn index 584d01b73..20626dbd8 100644 --- a/aot/BUILD.gn +++ b/aot/BUILD.gn @@ -17,20 +17,12 @@ import("//build/ohos.gni") ohos_executable("es2panda") { sources = [ "main.cpp", - "options.cpp", ] include_dirs = [ "$target_gen_dir", ] - build_gen_root = rebase_path(root_gen_dir) - - cflags_cc = [ - # TODO(vpukhov): adjust es2panda path - "-DDEFAULT_ARKTSCONFIG=\"$build_gen_root/plugins/es2panda/generated/arktsconfig.json\"" - ] - configs = [ sdk_libc_secshared_config, "$ark_root:ark_config", @@ -45,6 +37,7 @@ ohos_executable("es2panda") { deps = [ "$ark_es2panda_root:libes2panda_frontend_static", + "$ark_es2panda_root:libes2panda_public_frontend_static", "$ark_root/assembler:libarkassembler_frontend_static", "$ark_root/libpandafile:libarkfile_frontend_static", "$ark_root/libpandabase:libarkbase_frontend_static", @@ -63,4 +56,3 @@ ohos_executable("es2panda") { install_enable = true subsystem_name = "$ark_subsystem_name" } - diff --git a/aot/CMakeLists.txt b/aot/CMakeLists.txt index 0d453aac8..7e1e7788d 100644 --- a/aot/CMakeLists.txt +++ b/aot/CMakeLists.txt @@ -12,12 +12,11 @@ # limitations under the License. set(ES2PANDA_AOT_SRC - options.cpp main.cpp ) panda_add_executable(es2panda ${ES2PANDA_AOT_SRC}) -panda_target_link_libraries(es2panda es2panda-lib arkassembler arkbytecodeopt) +panda_target_link_libraries(es2panda es2panda-public es2panda-lib arkassembler arkbytecodeopt) panda_target_include_directories(es2panda PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) panda_target_include_directories(es2panda PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..) @@ -31,8 +30,4 @@ panda_target_compile_options(es2panda PRIVATE -Werror=shadow ) -panda_target_compile_definitions(es2panda - PRIVATE "DEFAULT_ARKTSCONFIG=\"${GENERATED_DIR}/arktsconfig.json\"" -) - panda_add_sanitizers(TARGET es2panda SANITIZERS ${PANDA_SANITIZERS_LIST}) diff --git a/aot/main.cpp b/aot/main.cpp index 2e02b1a3c..cfe42af47 100644 --- a/aot/main.cpp +++ b/aot/main.cpp @@ -18,9 +18,10 @@ #include "compiler/compiler_logger.h" #include "mem/arena_allocator.h" #include "mem/pool_manager.h" -#include "options.h" #include "es2panda.h" #include "util/arktsconfig.h" +#include "util/generateBin.h" +#include "util/options.h" #include #include @@ -48,73 +49,7 @@ public: } }; -static int GenerateProgram(panda::pandasm::Program *prog, const Options *options) -{ - std::map stat; - std::map *statp = options->OptLevel() != 0 ? &stat : nullptr; - panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps maps {}; - panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps *mapsp = options->OptLevel() != 0 ? &maps : nullptr; - -#ifdef PANDA_WITH_BYTECODE_OPTIMIZER - if (options->OptLevel() != 0) { - panda::Logger::ComponentMask component_mask; - component_mask.set(panda::Logger::Component::ASSEMBLER); - component_mask.set(panda::Logger::Component::COMPILER); - component_mask.set(panda::Logger::Component::BYTECODE_OPTIMIZER); - - panda::Logger::InitializeStdLogging(Logger::LevelFromString(options->LogLevel()), component_mask); - - if (!panda::pandasm::AsmEmitter::Emit(options->CompilerOutput(), *prog, statp, mapsp, true)) { - std::cerr << "Failed to emit binary data: " << panda::pandasm::AsmEmitter::GetLastError() << std::endl; - return 1; - } - - panda::bytecodeopt::OPTIONS.SetOptLevel(options->OptLevel()); - // Set default value instead of maximum set in panda::bytecodeopt::SetCompilerOptions() - panda::compiler::CompilerLogger::Init({"all"}); - panda::compiler::OPTIONS.SetCompilerMaxBytecodeSize(panda::compiler::OPTIONS.GetCompilerMaxBytecodeSize()); - panda::bytecodeopt::OptimizeBytecode(prog, mapsp, options->CompilerOutput(), options->IsDynamic(), true); - } -#endif - - if (options->CompilerOptions().dump_asm) { - es2panda::Compiler::DumpAsm(prog); - } - - if (!panda::pandasm::AsmEmitter::AssignProfileInfo(prog)) { - std::cerr << "AssignProfileInfo failed" << std::endl; - return 1; - } - - if (!panda::pandasm::AsmEmitter::Emit(options->CompilerOutput(), *prog, statp, mapsp, true)) { - std::cerr << "Failed to emit binary data: " << panda::pandasm::AsmEmitter::GetLastError() << std::endl; - return 1; - } - - if (options->SizeStat()) { - size_t total_size = 0; - std::cout << "Panda file size statistic:" << std::endl; - constexpr std::array INFO_STATS = {"instructions_number", "codesize"}; - - for (const auto &[name, size] : stat) { - if (find(INFO_STATS.begin(), INFO_STATS.end(), name) != INFO_STATS.end()) { - continue; - } - std::cout << name << " section: " << size << std::endl; - total_size += size; - } - - for (const auto &name : INFO_STATS) { - std::cout << name << ": " << stat.at(std::string(name)) << std::endl; - } - - std::cout << "total: " << total_size << std::endl; - } - - return 0; -} - -static int CompileFromSource(es2panda::Compiler &compiler, es2panda::SourceFile &input, Options *options) +static int CompileFromSource(es2panda::Compiler &compiler, es2panda::SourceFile &input, util::Options *options) { auto program = std::unique_ptr {compiler.Compile(input, options->CompilerOptions())}; @@ -127,16 +62,16 @@ static int CompileFromSource(es2panda::Compiler &compiler, es2panda::SourceFile } std::cout << err.TypeString() << ": " << err.Message(); - std::cout << " [" << (err.File().empty() ? BaseName(options->SourceFile()) : BaseName(err.File())) << ":" - << err.Line() << ":" << err.Col() << "]" << std::endl; + std::cout << " [" << (err.File().empty() ? util::BaseName(options->SourceFile()) : util::BaseName(err.File())) + << ":" << err.Line() << ":" << err.Col() << "]" << std::endl; return err.ErrorCode(); } - return GenerateProgram(program.get(), options); + return util::GenerateProgram(program.get(), options, [](std::string const &str) { std::cerr << str << std::endl; }); } -static int CompileFromConfig(es2panda::Compiler &compiler, Options *options) +static int CompileFromConfig(es2panda::Compiler &compiler, util::Options *options) { auto compilation_list = FindProjectSources(options->CompilerOptions().arkts_config); if (compilation_list.empty()) { @@ -173,7 +108,7 @@ static int CompileFromConfig(es2panda::Compiler &compiler, Options *options) static int Run(int argc, const char **argv) { - auto options = std::make_unique(); + auto options = std::make_unique(); if (!options->Parse(argc, argv)) { std::cerr << options->ErrorMsg() << std::endl; diff --git a/checker/ETSchecker.h b/checker/ETSchecker.h index 63d49b08e..522610b8b 100644 --- a/checker/ETSchecker.h +++ b/checker/ETSchecker.h @@ -85,7 +85,7 @@ using GlobalArraySignatureMap = ArenaUnorderedMap; using DynamicCallIntrinsicsMap = ArenaUnorderedMap>; using DynamicLambdaObjectSignatureMap = ArenaUnorderedMap; -class ETSChecker : public Checker { +class ETSChecker final : public Checker { public: explicit ETSChecker() // NOLINTNEXTLINE(readability-redundant-member-init) diff --git a/parser/ETSparser.h b/parser/ETSparser.h index f9ca02d76..375cf5704 100644 --- a/parser/ETSparser.h +++ b/parser/ETSparser.h @@ -27,7 +27,7 @@ enum class PrimitiveType; } // namespace panda::es2panda::ir namespace panda::es2panda::parser { -class ETSParser : public TypedParser { +class ETSParser final : public TypedParser { public: ETSParser(Program *program, const CompilerOptions &options, ParserStatus status = ParserStatus::NO_OPTS) : TypedParser(program, options, status), global_program_(GetProgram()), parsed_sources_({}) diff --git a/public/es2panda_lib.cpp b/public/es2panda_lib.cpp new file mode 100644 index 000000000..beea4e9fd --- /dev/null +++ b/public/es2panda_lib.cpp @@ -0,0 +1,365 @@ +#include "es2panda_lib.h" +#include + +#include "generated/signatures.h" +#include "es2panda.h" +#include "assembler/assembly-program.h" +#include "binder/ETSBinder.h" +#include "checker/ETSAnalyzer.h" +#include "checker/ETSchecker.h" +#include "compiler/core/compileQueue.h" +#include "compiler/core/ETSCompiler.h" +#include "compiler/core/ETSemitter.h" +#include "compiler/core/ETSGen.h" +#include "compiler/core/regSpiller.h" +#include "compiler/lowering/phase.h" +#include "ir/astNode.h" +#include "ir/expressions/identifier.h" +#include "parser/ETSparser.h" +#include "parser/context/parserContext.h" +#include "parser/program/program.h" +#include "util/generateBin.h" +#include "util/options.h" + +namespace panda::es2panda::public_lib { +struct ConfigImpl { + std::unique_ptr options; +}; + +struct Context { + ConfigImpl *config = nullptr; + std::string source_file_name; + std::string input; + std::unique_ptr source_file; + std::unique_ptr allocator; + std::unique_ptr queue; + + std::unique_ptr parser_program; + std::unique_ptr parser; + std::unique_ptr checker; + std::unique_ptr analyzer; + std::unique_ptr compiler_context; + std::unique_ptr emitter; + std::unique_ptr program; + + es2panda_ContextState state = ES2PANDA_STATE_NEW; + std::string error_message; +}; + +extern "C" es2panda_Config *CreateConfig(int args, char const **argv) +{ + constexpr auto COMPILER_SIZE = 256_MB; + + mem::MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); + PoolManager::Initialize(PoolType::MMAP); + + auto options = std::make_unique(); + if (!options->Parse(args, argv)) { + // TODO(gogabr): report option errors properly. + std::cerr << options->ErrorMsg() << std::endl; + return nullptr; + } + Logger::ComponentMask mask {}; + mask.set(Logger::Component::ES2PANDA); + Logger::InitializeStdLogging(Logger::LevelFromString(options->LogLevel()), mask); + + auto *res = new ConfigImpl; + res->options = std::move(options); + return reinterpret_cast(res); +} + +extern "C" void DestroyConfig(es2panda_Config *config) +{ + PoolManager::Finalize(); + mem::MemConfig::Finalize(); + + delete reinterpret_cast(config); +} + +static void CompileJob(compiler::CompilerContext *context, binder::FunctionScope *scope, + compiler::ProgramElement *program_element) +{ + compiler::StaticRegSpiller reg_spiller; + ArenaAllocator allocator {SpaceType::SPACE_TYPE_COMPILER, nullptr, true}; + compiler::ETSCompiler ast_compiler {}; + compiler::ETSGen cg {&allocator, ®_spiller, context, scope, program_element, &ast_compiler}; + compiler::ETSFunctionEmitter func_emitter {&cg, program_element}; + func_emitter.Generate(); +} + +static es2panda_Context *CreateContext(es2panda_Config *config, std::string const &&source, + std::string const &&file_name) +{ + auto *cfg = reinterpret_cast(config); + auto *res = new Context; + res->config = cfg; + res->input = source; + res->source_file_name = file_name; + + try { + res->source_file = std::make_unique(res->source_file_name, res->input, cfg->options->ParseModule()); + res->allocator = std::make_unique(SpaceType::SPACE_TYPE_COMPILER, nullptr, true); + res->queue = std::make_unique(cfg->options->ThreadCount()); + + auto *binder = res->allocator->New(res->allocator.get()); + res->parser_program = std::make_unique(res->allocator.get(), binder); + res->parser_program->MarkEntry(); + res->parser = std::make_unique(res->parser_program.get(), cfg->options->CompilerOptions(), + parser::ParserStatus::NO_OPTS); + res->checker = std::make_unique(); + res->analyzer = std::make_unique(res->checker.get()); + res->checker->SetAnalyzer(res->analyzer.get()); + + binder->SetProgram(res->parser_program.get()); + + res->compiler_context = std::make_unique( + binder, res->checker.get(), cfg->options->CompilerOptions(), CompileJob); + binder->SetCompilerContext(res->compiler_context.get()); + res->emitter = std::make_unique(res->compiler_context.get()); + res->compiler_context->SetEmitter(res->emitter.get()); + res->program = nullptr; + res->state = ES2PANDA_STATE_NEW; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + res->error_message = ss.str(); + res->state = ES2PANDA_STATE_ERROR; + } + return reinterpret_cast(res); +} + +extern "C" es2panda_Context *CreateContextFromFile(es2panda_Config *config, char const *source_file_name) +{ + std::ifstream input_stream; + input_stream.open(source_file_name); + if (input_stream.fail()) { + auto *res = new Context; + res->error_message = "Failed to open file: "; + res->error_message.append(source_file_name); + return reinterpret_cast(res); + } + std::stringstream ss; + ss << input_stream.rdbuf(); + if (input_stream.fail()) { + auto *res = new Context; + res->error_message = "Failed to read file: "; + res->error_message.append(source_file_name); + return reinterpret_cast(res); + } + return CreateContext(config, ss.str(), source_file_name); +} + +extern "C" es2panda_Context *CreateContextFromString(es2panda_Config *config, char const *source, char const *file_name) +{ + // TODO(gogabr): avoid copying source. + return CreateContext(config, source, file_name); +} + +static Context *Parse(Context *ctx) +{ + if (ctx->state != ES2PANDA_STATE_NEW) { + ctx->state = ES2PANDA_STATE_ERROR; + ctx->error_message = "Bad state at entry to Parse, needed NEW"; + return ctx; + } + try { + ctx->parser->ParseScript(*ctx->source_file, ctx->config->options->CompilerOptions().compilation_mode == + CompilationMode::GEN_STD_LIB); + ctx->state = ES2PANDA_STATE_PARSED; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + ctx->error_message = ss.str(); + ctx->state = ES2PANDA_STATE_ERROR; + } + + return ctx; +} + +static Context *Check(Context *ctx) +{ + if (ctx->state < ES2PANDA_STATE_PARSED) { + ctx = Parse(ctx); + } + + if (ctx->state == ES2PANDA_STATE_ERROR) { + return ctx; + } + + ASSERT(ctx->state == ES2PANDA_STATE_PARSED); + + try { + ctx->compiler_context->Checker()->StartChecker(ctx->compiler_context->Binder(), + ctx->config->options->CompilerOptions()); + ctx->state = ES2PANDA_STATE_CHECKED; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + ctx->error_message = ss.str(); + ctx->state = ES2PANDA_STATE_ERROR; + } + return ctx; +} + +static Context *Lower(Context *ctx) +{ + if (ctx->state < ES2PANDA_STATE_CHECKED) { + ctx = Check(ctx); + } + + if (ctx->state == ES2PANDA_STATE_ERROR) { + return ctx; + } + + ASSERT(ctx->state == ES2PANDA_STATE_CHECKED); + + try { + for (auto *phase : compiler::GetETSPhaseList()) { + phase->Apply(ctx->compiler_context.get(), ctx->compiler_context->Binder()->Program()); + } + + ctx->state = ES2PANDA_STATE_LOWERED; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + ctx->error_message = ss.str(); + ctx->state = ES2PANDA_STATE_ERROR; + } + + return ctx; +} + +static Context *GenerateAsm(Context *ctx) +{ + if (ctx->state < ES2PANDA_STATE_LOWERED) { + ctx = Lower(ctx); + } + + if (ctx->state == ES2PANDA_STATE_ERROR) { + return ctx; + } + + ASSERT(ctx->state == ES2PANDA_STATE_LOWERED); + + auto *emitter = ctx->compiler_context->GetEmitter(); + try { + emitter->GenAnnotation(); + + // Handle context literals. + uint32_t index = 0; + for (const auto &buff : ctx->compiler_context->ContextLiterals()) { + emitter->AddLiteralBuffer(buff, index++); + } + + emitter->LiteralBufferIndex() += ctx->compiler_context->ContextLiterals().size(); + + /* Main thread can also be used instead of idling */ + ctx->queue->Schedule(ctx->compiler_context.get()); + ctx->queue->Consume(); + ctx->queue->Wait( + [emitter](compiler::CompileJob *job) { emitter->AddProgramElement(job->GetProgramElement()); }); + ASSERT(ctx->program == nullptr); + ctx->program = std::unique_ptr { + emitter->Finalize(ctx->compiler_context->DumpDebugInfo(), compiler::Signatures::ETS_GLOBAL)}; + + ctx->state = ES2PANDA_STATE_ASM_GENERATED; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + ctx->error_message = ss.str(); + ctx->state = ES2PANDA_STATE_ERROR; + } + return ctx; +} + +Context *GenerateBin(Context *ctx) +{ + if (ctx->state < ES2PANDA_STATE_ASM_GENERATED) { + ctx = GenerateAsm(ctx); + } + + if (ctx->state == ES2PANDA_STATE_ERROR) { + return ctx; + } + + ASSERT(ctx->state == ES2PANDA_STATE_ASM_GENERATED); + + try { + ASSERT(ctx->program != nullptr); + util::GenerateProgram(ctx->program.get(), ctx->config->options.get(), + [ctx](const std::string &str) { ctx->error_message = str; }); + + ctx->state = ES2PANDA_STATE_BIN_GENERATED; + } catch (Error &e) { + std::stringstream ss; + ss << e.TypeString() << ": " << e.Message() << "[" << e.File() << ":" << e.Line() << "," << e.Col() << "]"; + ctx->error_message = ss.str(); + ctx->state = ES2PANDA_STATE_ERROR; + } + return ctx; +} + +extern "C" es2panda_Context *ProceedToState(es2panda_Context *context, es2panda_ContextState state) +{ + auto *ctx = reinterpret_cast(context); + switch (state) { + case ES2PANDA_STATE_NEW: + break; + case ES2PANDA_STATE_PARSED: + ctx = Parse(ctx); + break; + case ES2PANDA_STATE_CHECKED: + ctx = Check(ctx); + break; + case ES2PANDA_STATE_LOWERED: + ctx = Lower(ctx); + break; + case ES2PANDA_STATE_ASM_GENERATED: + ctx = GenerateAsm(ctx); + break; + case ES2PANDA_STATE_BIN_GENERATED: + ctx = GenerateBin(ctx); + break; + default: + ctx->error_message = "It does not make sense to request stage"; + ctx->state = ES2PANDA_STATE_ERROR; + break; + } + return reinterpret_cast(ctx); +} + +extern "C" void DestroyContext(es2panda_Context *context) +{ + auto *s = reinterpret_cast(context); + delete s; +} + +extern "C" es2panda_ContextState ContextState(es2panda_Context *context) +{ + auto *s = reinterpret_cast(context); + return s->state; +} + +extern "C" char const *ContextErrorMessage(es2panda_Context *context) +{ + auto *s = reinterpret_cast(context); + return s->error_message.c_str(); +} + +es2panda_Impl IMPL = {ES2PANDA_LIB_VERSION, + + CreateConfig, DestroyConfig, + + CreateContextFromFile, CreateContextFromString, ProceedToState, DestroyContext, + + ContextState, ContextErrorMessage}; + +} // namespace panda::es2panda::public_lib + +extern "C" es2panda_Impl const *es2panda_GetImpl(int version) +{ + if (version != ES2PANDA_LIB_VERSION) { + return nullptr; + } + return &panda::es2panda::public_lib::IMPL; +} diff --git a/public/es2panda_lib.h b/public/es2panda_lib.h new file mode 100644 index 000000000..f7402d220 --- /dev/null +++ b/public/es2panda_lib.h @@ -0,0 +1,50 @@ +#ifndef ES2PANDA_LIB +#define ES2PANDA_LIB + +#ifdef __cplusplus +extern "C" { +#endif + +// Switch off the linter for C header +// NOLINTBEGIN + +#define ES2PANDA_LIB_VERSION 0 + +typedef void es2panda_Config; +typedef void es2panda_Context; + +enum es2panda_ContextState { + ES2PANDA_STATE_NEW, + ES2PANDA_STATE_PARSED, + ES2PANDA_STATE_CHECKED, + ES2PANDA_STATE_LOWERED, + ES2PANDA_STATE_ASM_GENERATED, + ES2PANDA_STATE_BIN_GENERATED, + + ES2PANDA_STATE_ERROR +}; +typedef enum es2panda_ContextState es2panda_ContextState; + +struct es2panda_Impl { + int version; + + es2panda_Config *(*CreateConfig)(int argc, char const **argv); + void (*DestroyConfig)(es2panda_Config *config); + + es2panda_Context *(*CreateContextFromFile)(es2panda_Config *config, char const *source_file_name); + es2panda_Context *(*CreateContextFromString)(es2panda_Config *config, char const *source, char const *file_name); + es2panda_Context *(*ProceedToState)(es2panda_Context *context, es2panda_ContextState state); // context is consumed + void (*DestroyContext)(es2panda_Context *context); + + es2panda_ContextState (*ContextState)(es2panda_Context *context); + char const *(*ContextErrorMessage)(es2panda_Context *context); +}; + +struct es2panda_Impl const *es2panda_GetImpl(int version); +// NOLINTEND + +#ifdef __cplusplus +} +#endif + +#endif // ES2PANDA_LIB diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ff81c968e..f0310c04a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -75,6 +75,16 @@ if(PANDA_WITH_ETS) add_dependencies(es2panda-regression-tests es2panda-regression-tests-${CURRENT_GROUP}) endforeach() + panda_add_gtest( + NAME es2panda_public_tests + SOURCES + public/es2panda_public_test.cpp + LIBRARIES + es2panda-public es2panda-lib arkassembler arkbytecodeopt + SANITIZERS + ${PANDA_SANITIZERS_LIST} + ) + add_dependencies(es2panda_tests es2panda-regression-tests) if(TARGET ets_tests) add_dependencies(ets_tests es2panda_tests) diff --git a/test/public/es2panda_public_test.cpp b/test/public/es2panda_public_test.cpp new file mode 100644 index 000000000..edc32c90f --- /dev/null +++ b/test/public/es2panda_public_test.cpp @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2021-2022 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 +#include "macros.h" +#include "plugins/ecmascript/es2panda/public/es2panda_lib.h" + +class Es2PandaLibTest : public testing::Test { +public: + Es2PandaLibTest() + { + impl_ = es2panda_GetImpl(ES2PANDA_LIB_VERSION); + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + char const *argv[] = {"test"}; + cfg_ = impl_->CreateConfig(1, argv); + } + + ~Es2PandaLibTest() override + { + impl_->DestroyConfig(cfg_); + } + + NO_COPY_SEMANTIC(Es2PandaLibTest); + NO_MOVE_SEMANTIC(Es2PandaLibTest); + +protected: + // NOLINTBEGIN(misc-non-private-member-variables-in-classes) + es2panda_Impl const *impl_; + es2panda_Config *cfg_; + // NOLINTEND(misc-non-private-member-variables-in-classes) +}; + +TEST_F(Es2PandaLibTest, NoError) +{ + es2panda_Context *ctx = impl_->CreateContextFromString(cfg_, "function main() {}", "no-error.ets"); + impl_->ProceedToState(ctx, ES2PANDA_STATE_ASM_GENERATED); // don't produce any object files + ASSERT_EQ(impl_->ContextState(ctx), ES2PANDA_STATE_ASM_GENERATED); + impl_->DestroyContext(ctx); +} + +TEST_F(Es2PandaLibTest, TypeError) +{ + es2panda_Context *ctx = + impl_->CreateContextFromString(cfg_, "function main() { let x: int = \"\" }", "no-error.ets"); + impl_->ProceedToState(ctx, ES2PANDA_STATE_ASM_GENERATED); // don't produce any object files + ASSERT_EQ(impl_->ContextState(ctx), ES2PANDA_STATE_ERROR); + ASSERT_EQ(std::string(impl_->ContextErrorMessage(ctx)), + "TypeError: Initializers type is not assignable to the target type[no-error.ets:1,32]"); + impl_->DestroyContext(ctx); +} diff --git a/util/generateBin.cpp b/util/generateBin.cpp new file mode 100644 index 000000000..35bc28770 --- /dev/null +++ b/util/generateBin.cpp @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2021-2023 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 "generateBin.h" +#include "bytecode_optimizer/bytecodeopt_options.h" +#include "bytecode_optimizer/optimize_bytecode.h" +#include "compiler/compiler_logger.h" +#include "compiler/compiler_options.h" + +namespace panda::es2panda::util { + +int GenerateProgram(panda::pandasm::Program *prog, const util::Options *options, const ReporterFun &reporter) +{ + std::map stat; + std::map *statp = options->OptLevel() != 0 ? &stat : nullptr; + panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps maps {}; + panda::pandasm::AsmEmitter::PandaFileToPandaAsmMaps *mapsp = options->OptLevel() != 0 ? &maps : nullptr; + +#ifdef PANDA_WITH_BYTECODE_OPTIMIZER + if (options->OptLevel() != 0) { + panda::Logger::ComponentMask component_mask; + component_mask.set(panda::Logger::Component::ASSEMBLER); + component_mask.set(panda::Logger::Component::COMPILER); + component_mask.set(panda::Logger::Component::BYTECODE_OPTIMIZER); + + panda::Logger::InitializeStdLogging(Logger::LevelFromString(options->LogLevel()), component_mask); + + if (!panda::pandasm::AsmEmitter::Emit(options->CompilerOutput(), *prog, statp, mapsp, true)) { + reporter("Failed to emit binary data: " + panda::pandasm::AsmEmitter::GetLastError()); + return 1; + } + + panda::bytecodeopt::OPTIONS.SetOptLevel(options->OptLevel()); + // Set default value instead of maximum set in panda::bytecodeopt::SetCompilerOptions() + panda::compiler::CompilerLogger::Init({"all"}); + panda::compiler::OPTIONS.SetCompilerMaxBytecodeSize(panda::compiler::OPTIONS.GetCompilerMaxBytecodeSize()); + panda::bytecodeopt::OptimizeBytecode(prog, mapsp, options->CompilerOutput(), options->IsDynamic(), true); + } +#endif + + if (options->CompilerOptions().dump_asm) { + es2panda::Compiler::DumpAsm(prog); + } + + if (!panda::pandasm::AsmEmitter::AssignProfileInfo(prog)) { + reporter("AssignProfileInfo failed"); + return 1; + } + + if (!panda::pandasm::AsmEmitter::Emit(options->CompilerOutput(), *prog, statp, mapsp, true)) { + reporter("Failed to emit binary data: " + panda::pandasm::AsmEmitter::GetLastError()); + return 1; + } + + if (options->SizeStat()) { + size_t total_size = 0; + std::cout << "Panda file size statistic:" << std::endl; + constexpr std::array INFO_STATS = {"instructions_number", "codesize"}; + + for (const auto &[name, size] : stat) { + if (find(INFO_STATS.begin(), INFO_STATS.end(), name) != INFO_STATS.end()) { + continue; + } + std::cout << name << " section: " << size << std::endl; + total_size += size; + } + + for (const auto &name : INFO_STATS) { + std::cout << name << ": " << stat.at(std::string(name)) << std::endl; + } + + std::cout << "total: " << total_size << std::endl; + } + + return 0; +} + +} // namespace panda::es2panda::util diff --git a/util/generateBin.h b/util/generateBin.h new file mode 100644 index 000000000..531ca8f38 --- /dev/null +++ b/util/generateBin.h @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2021-2023 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 "plugins/ecmascript/es2panda/parser/program/program.h" +#include "plugins/ecmascript/es2panda/util/options.h" + +#ifndef ES2PANDA_UTIL_GENERATE_BIN_H +#define ES2PANDA_UTIL_GENREATE_BIN_H + +namespace panda::es2panda::util { + +using ReporterFun = std::function; + +int GenerateProgram(panda::pandasm::Program *prog, const util::Options *options, const ReporterFun &reporter); + +} // namespace panda::es2panda::util + +#endif diff --git a/aot/options.cpp b/util/options.cpp similarity index 99% rename from aot/options.cpp rename to util/options.cpp index c5a15797f..3547bd5e5 100644 --- a/aot/options.cpp +++ b/util/options.cpp @@ -19,7 +19,7 @@ #include -namespace panda::es2panda::aot { +namespace panda::es2panda::util { template T RemoveExtension(T const &filename) { @@ -303,4 +303,4 @@ bool Options::Parse(int argc, const char **argv) return true; } -} // namespace panda::es2panda::aot +} // namespace panda::es2panda::util diff --git a/aot/options.h b/util/options.h similarity index 96% rename from aot/options.h rename to util/options.h index c7e07c697..f73dcc1a9 100644 --- a/aot/options.h +++ b/util/options.h @@ -13,8 +13,8 @@ * limitations under the License. */ -#ifndef ES2PANDA_AOT_OPTIONS_H -#define ES2PANDA_AOT_OPTIONS_H +#ifndef ES2PANDA_UTIL_OPTIONS_H +#define ES2PANDA_UTIL_OPTIONS_H #include "libpandabase/os/file.h" #include "es2panda.h" @@ -29,7 +29,7 @@ class PandArgParser; class PandaArg; } // namespace panda -namespace panda::es2panda::aot { +namespace panda::es2panda::util { enum class OptionFlags : uint32_t { DEFAULT = 0U, PARSE_ONLY = 1U << 0U, @@ -175,6 +175,6 @@ private: bool list_files_ {false}; util::LogLevel log_level_ {util::LogLevel::ERROR}; }; -} // namespace panda::es2panda::aot +} // namespace panda::es2panda::util -#endif // AOT_OPTIONS_H +#endif // UTIL_OPTIONS_H -- Gitee From b3613c787bdf32f2b38e836781f3811336559037 Mon Sep 17 00:00:00 2001 From: Georgy Bronnikov Date: Sat, 7 Oct 2023 15:12:18 +0300 Subject: [PATCH 2/3] es2panda_lib: add functions for program manipulation Signed-off-by: Georgy Bronnikov --- public/es2panda_lib.cpp | 170 ++++++++++++++++++++++++++- public/es2panda_lib.h | 29 +++++ test/public/es2panda_public_test.cpp | 48 +++++++- 3 files changed, 240 insertions(+), 7 deletions(-) diff --git a/public/es2panda_lib.cpp b/public/es2panda_lib.cpp index beea4e9fd..ad9bab55b 100644 --- a/public/es2panda_lib.cpp +++ b/public/es2panda_lib.cpp @@ -46,6 +46,28 @@ struct Context { std::string error_message; }; +static char const *StringViewToCString(ArenaAllocator *allocator, util::StringView const sv) +{ + std::string_view utf8 = sv.Utf8(); + if (utf8.data()[utf8.size()] == '\0') { + // Avoid superfluous allocation. + return utf8.data(); + } + char *res = reinterpret_cast(allocator->Alloc(utf8.size() + 1)); + memmove(res, utf8.cbegin(), utf8.size()); + res[utf8.size()] = '\0'; + return res; +} + +static char const *ArenaStrdup(ArenaAllocator *allocator, char const *src) +{ + size_t len = strlen(src); + char *res = reinterpret_cast(allocator->Alloc(len + 1)); + memmove(res, src, len); + res[len] = '\0'; + return res; +} + extern "C" es2panda_Config *CreateConfig(int args, char const **argv) { constexpr auto COMPILER_SIZE = 256_MB; @@ -346,13 +368,153 @@ extern "C" char const *ContextErrorMessage(es2panda_Context *context) return s->error_message.c_str(); } -es2panda_Impl IMPL = {ES2PANDA_LIB_VERSION, +extern "C" es2panda_Program *ContextProgram(es2panda_Context *context) +{ + auto *ctx = reinterpret_cast(context); + return reinterpret_cast(ctx->compiler_context->Binder()->Program()); +} + +extern "C" es2panda_AstNode *ProgramAst(es2panda_Program *program) +{ + auto *pgm = reinterpret_cast(program); + return reinterpret_cast(pgm->Ast()); +} + +using ExternalSourceEntry = std::pair *>; + +extern "C" es2panda_ExternalSource **ProgramExternalSources(es2panda_Program *program, size_t *len_p) +{ + auto *pgm = reinterpret_cast(program); + auto *allocator = pgm->Binder()->Allocator(); + auto *vec = allocator->New>(allocator->Adapter()); + + for (auto &[e_name, e_programs] : pgm->ExternalSources()) { + vec->push_back(allocator->New(StringViewToCString(allocator, e_name), &e_programs)); + } + + *len_p = vec->size(); + return reinterpret_cast(vec->data()); +} + +extern "C" char const *ExternalSourceName(es2panda_ExternalSource *e_source) +{ + auto *entry = reinterpret_cast(e_source); + return entry->first; +} + +extern "C" es2panda_Program **ExternalSourcePrograms(es2panda_ExternalSource *e_source, size_t *len_p) +{ + auto *entry = reinterpret_cast(e_source); + *len_p = entry->second->size(); + return reinterpret_cast(entry->second->data()); +} + +extern "C" es2panda_Type *AstNodeType(es2panda_AstNode *ast) +{ + auto *node = reinterpret_cast(ast); + // Need to work with other TypeAstNodes + if (node->IsExpression()) { + return reinterpret_cast(node->AsExpression()->TsType()); + } else { + return nullptr; + } +} + +extern "C" void SetAstNodeType(es2panda_AstNode *ast, es2panda_Type *type) +{ + auto *node = reinterpret_cast(ast); + auto *tp = reinterpret_cast(type); + // Need to work with other TypeAstNodes + if (node->IsExpression()) { + node->AsExpression()->SetTsType(tp); + } else { + UNREACHABLE(); + } +} + +extern "C" void AstNodeForEach(es2panda_AstNode *ast, void (*func)(es2panda_AstNode *, void *), void *arg) +{ + auto *node = reinterpret_cast(ast); + func(ast, arg); + node->IterateRecursively([=](ir::AstNode *child) { func(reinterpret_cast(child), arg); }); +} - CreateConfig, DestroyConfig, +extern "C" bool IsIdentifier(es2panda_AstNode *ast) +{ + auto *node = reinterpret_cast(ast); + return node->IsIdentifier(); +} - CreateContextFromFile, CreateContextFromString, ProceedToState, DestroyContext, +extern "C" es2panda_AstNode *CreateIdentifier(es2panda_Context *context, char const *name, + es2panda_AstNode *type_annotations) +{ + auto *ctx = reinterpret_cast(context); + auto *name_copy = ArenaStrdup(ctx->allocator.get(), name); + auto *tp_ann = reinterpret_cast(type_annotations); + ASSERT(tp_ann->IsExpression() && tp_ann->AsExpression()->IsTypeNode()); + + auto *res = ctx->allocator->New(util::StringView {name_copy}, tp_ann->AsExpression()->AsTypeNode(), + ctx->allocator.get()); + + return reinterpret_cast(res); +} + +extern "C" char const *IdentifierName(es2panda_Context *context, es2panda_AstNode *identifier) +{ + auto *ctx = reinterpret_cast(context); + auto *id = reinterpret_cast(identifier); + ASSERT(id->IsIdentifier()); + + return StringViewToCString(ctx->allocator.get(), id->AsIdentifier()->Name()); +} - ContextState, ContextErrorMessage}; +extern "C" es2panda_AstNode *IdentifierTypeAnnotation(es2panda_AstNode *identifier) +{ + auto *id = reinterpret_cast(identifier); + ASSERT(id->IsIdentifier()); + + return reinterpret_cast(id->AsIdentifier()->TypeAnnotation()); +} + +extern "C" es2panda_Variable *IdentifierVariable(es2panda_AstNode *identifier) +{ + auto *id = reinterpret_cast(identifier); + ASSERT(id->IsIdentifier()); + + return reinterpret_cast(id->AsIdentifier()->Variable()); +} + +es2panda_Impl IMPL = { + ES2PANDA_LIB_VERSION, + + CreateConfig, + DestroyConfig, + + CreateContextFromFile, + CreateContextFromString, + ProceedToState, + DestroyContext, + + ContextState, + ContextErrorMessage, + + ContextProgram, + ProgramAst, + ProgramExternalSources, + ExternalSourceName, + ExternalSourcePrograms, + + AstNodeType, + SetAstNodeType, + AstNodeForEach, + + IsIdentifier, + + CreateIdentifier, + IdentifierName, + IdentifierTypeAnnotation, + IdentifierVariable, +}; } // namespace panda::es2panda::public_lib diff --git a/public/es2panda_lib.h b/public/es2panda_lib.h index f7402d220..09964ea4b 100644 --- a/public/es2panda_lib.h +++ b/public/es2panda_lib.h @@ -1,6 +1,8 @@ #ifndef ES2PANDA_LIB #define ES2PANDA_LIB +#include + #ifdef __cplusplus extern "C" { #endif @@ -13,6 +15,13 @@ extern "C" { typedef void es2panda_Config; typedef void es2panda_Context; +typedef void es2panda_Program; +typedef void es2panda_ExternalSource; +typedef void es2panda_AstNode; +typedef void es2panda_Type; +typedef void es2panda_Variable; +typedef void es2panda_Scope; + enum es2panda_ContextState { ES2PANDA_STATE_NEW, ES2PANDA_STATE_PARSED, @@ -38,6 +47,26 @@ struct es2panda_Impl { es2panda_ContextState (*ContextState)(es2panda_Context *context); char const *(*ContextErrorMessage)(es2panda_Context *context); + + es2panda_Program *(*ContextProgram)(es2panda_Context *context); + es2panda_AstNode *(*ProgramAst)(es2panda_Program *program); + es2panda_ExternalSource **(*ProgramExternalSources)(es2panda_Program *program, size_t *len_p); + char const *(*ExternalSourceName)(es2panda_ExternalSource *e_source); + es2panda_Program **(*ExternalSourcePrograms)(es2panda_ExternalSource *e_source, size_t *len_p); + + es2panda_Type *(*AstNodeType)(es2panda_AstNode *ast); + void (*SetAstNodeType)(es2panda_AstNode *ast, es2panda_Type *type); + + void (*AstNodeForEach)(es2panda_AstNode *ast, void (*func)(es2panda_AstNode *, void *), void *arg); + + bool (*IsIdentifier)(es2panda_AstNode *ast); + + /* type_annotation may be null */ + es2panda_AstNode *(*CreateIdentifier)(es2panda_Context *context, char const *name, + es2panda_AstNode *type_annotation); + char const *(*IdentifierName)(es2panda_Context *context, es2panda_AstNode *identifier); + es2panda_AstNode *(*IdentifierTypeAnnotation)(es2panda_AstNode *identifier); + es2panda_Variable *(*IdentifierVariable)(es2panda_AstNode *identifier); }; struct es2panda_Impl const *es2panda_GetImpl(int version); diff --git a/test/public/es2panda_public_test.cpp b/test/public/es2panda_public_test.cpp index edc32c90f..1015eb8ef 100644 --- a/test/public/es2panda_public_test.cpp +++ b/test/public/es2panda_public_test.cpp @@ -53,10 +53,52 @@ TEST_F(Es2PandaLibTest, NoError) TEST_F(Es2PandaLibTest, TypeError) { es2panda_Context *ctx = - impl_->CreateContextFromString(cfg_, "function main() { let x: int = \"\" }", "no-error.ets"); - impl_->ProceedToState(ctx, ES2PANDA_STATE_ASM_GENERATED); // don't produce any object files + impl_->CreateContextFromString(cfg_, "function main() { let x: int = \"\" }", "type-error.ets"); + impl_->ProceedToState(ctx, ES2PANDA_STATE_ASM_GENERATED); ASSERT_EQ(impl_->ContextState(ctx), ES2PANDA_STATE_ERROR); ASSERT_EQ(std::string(impl_->ContextErrorMessage(ctx)), - "TypeError: Initializers type is not assignable to the target type[no-error.ets:1,32]"); + "TypeError: Initializers type is not assignable to the target type[type-error.ets:1,32]"); + impl_->DestroyContext(ctx); +} + +TEST_F(Es2PandaLibTest, ListIdentifiers) +{ + char const *text = R"XXX( +class C { + n: string = "oh" +} + +function main() { + let c = new C + console.log(c.n + 1) // type error, but not syntax error +} +)XXX"; + es2panda_Context *ctx = impl_->CreateContextFromString(cfg_, text, "list-ids.ets"); + ctx = impl_->ProceedToState(ctx, ES2PANDA_STATE_PARSED); + ASSERT_EQ(impl_->ContextState(ctx), ES2PANDA_STATE_PARSED); + + struct Arg { + es2panda_Impl const *impl; + es2panda_Context *ctx; + std::vector ids; + } arg; + arg.impl = impl_; + arg.ctx = ctx; + + auto func = [](es2panda_AstNode *ast, void *argp) { + auto *a = reinterpret_cast(argp); + if (a->impl->IsIdentifier(ast)) { + a->ids.push_back(a->impl->IdentifierName(a->ctx, ast)); + } + }; + + impl_->AstNodeForEach(impl_->ProgramAst(impl_->ContextProgram(ctx)), func, &arg); + + // TODO(gogabr): the list is too fragile + std::vector expected {"C", "n", "string", "constructor", "constructor", "ETSGLOBAL", + "_$init$_", "_$init$_", "main", "main", "c", "C", + "console", "log", "c", "n", ""}; + ASSERT_EQ(arg.ids, expected); + impl_->DestroyContext(ctx); } -- Gitee From 5536985a3434d0c74d47d881518e9fe963135e26 Mon Sep 17 00:00:00 2001 From: Georgy Bronnikov Date: Thu, 12 Oct 2023 21:25:24 +0300 Subject: [PATCH 3/3] Loadable plugins for es2panda Signed-off-by: Georgy Bronnikov --- BUILD.gn | 1 + CMakeLists.txt | 1 + aot/main.cpp | 22 ++++- checker/checker.h | 2 +- compiler/core/compilerImpl.cpp | 51 ++++++++--- compiler/core/compilerImpl.h | 19 ++++- compiler/lowering/phase.cpp | 6 +- es2panda.cpp | 6 +- es2panda.h | 16 +++- parser/ETSparser.h | 2 +- parser/parserImpl.h | 2 +- public/es2panda_lib.cpp | 121 ++++++++++++++------------- public/es2panda_lib.h | 22 ++++- public/public.h | 57 +++++++++++++ test/CMakeLists.txt | 14 ++++ test/public/e2p_test_plugin.c | 57 +++++++++++++ test/public/es2panda_public_test.cpp | 6 +- test/public/plugin_test.expected | 16 ++++ test/public/t.ets | 5 ++ util/options.cpp | 21 ++++- util/plugin.cpp | 55 ++++++++++++ util/plugin.h | 87 +++++++++++++++++++ 22 files changed, 496 insertions(+), 93 deletions(-) create mode 100644 public/public.h create mode 100644 test/public/e2p_test_plugin.c create mode 100644 test/public/plugin_test.expected create mode 100644 test/public/t.ets create mode 100644 util/plugin.cpp create mode 100644 util/plugin.h diff --git a/BUILD.gn b/BUILD.gn index 2db562256..85811d8de 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -343,6 +343,7 @@ libes2panda_sources = [ "util/bitset.cpp", "util/declgenEts2Ts.cpp", "util/helpers.cpp", + "util/plugin.cpp", "util/ustring.cpp", ] diff --git a/CMakeLists.txt b/CMakeLists.txt index 5877b2f10..38ca7b638 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -449,6 +449,7 @@ set(ES2PANDA_PUBLIC_SOURCES public/es2panda_lib.cpp util/generateBin.cpp util/options.cpp + util/plugin.cpp ) diff --git a/aot/main.cpp b/aot/main.cpp index cfe42af47..4f5a09fcd 100644 --- a/aot/main.cpp +++ b/aot/main.cpp @@ -22,6 +22,7 @@ #include "util/arktsconfig.h" #include "util/generateBin.h" #include "util/options.h" +#include "util/plugin.h" #include #include @@ -106,6 +107,21 @@ static int CompileFromConfig(es2panda::Compiler &compiler, util::Options *option return overall_res; } +static std::pair, bool> InitializePlugins(std::vector const &names) +{ + std::vector res; + for (auto &name : names) { + auto plugin = util::Plugin(util::StringView {name}); + if (!plugin.IsOk()) { + std::cerr << "Error: Failed to load plugin " << name << std::endl; + return {std::vector(), false}; + } + plugin.Initialize(); + res.push_back(std::move(plugin)); + } + return {std::move(res), true}; +} + static int Run(int argc, const char **argv) { auto options = std::make_unique(); @@ -118,7 +134,11 @@ static int Run(int argc, const char **argv) Logger::ComponentMask mask {}; mask.set(Logger::Component::ES2PANDA); Logger::InitializeStdLogging(Logger::LevelFromString(options->LogLevel()), mask); - es2panda::Compiler compiler(options->Extension(), options->ThreadCount()); + auto [plugins, ok] = InitializePlugins(options->CompilerOptions().plugins); + if (!ok) { + return 1; + } + es2panda::Compiler compiler(options->Extension(), options->ThreadCount(), std::move(plugins)); if (options->CompilerOptions().compilation_mode == CompilationMode::PROJECT) { return CompileFromConfig(compiler, options.get()); diff --git a/checker/checker.h b/checker/checker.h index 140727440..01bf351fd 100644 --- a/checker/checker.h +++ b/checker/checker.h @@ -69,7 +69,7 @@ using ArgRange = std::pair; class Checker { public: explicit Checker(); - ~Checker() = default; + virtual ~Checker() = default; NO_COPY_SEMANTIC(Checker); NO_MOVE_SEMANTIC(Checker); diff --git a/compiler/core/compilerImpl.cpp b/compiler/core/compilerImpl.cpp index f113b355c..4075cd0e4 100644 --- a/compiler/core/compilerImpl.cpp +++ b/compiler/core/compilerImpl.cpp @@ -15,6 +15,7 @@ #include "compilerImpl.h" +#include "es2panda.h" #include "compiler/core/compilerContext.h" #include "compiler/core/compileQueue.h" #include "compiler/core/compilerImpl.h" @@ -39,10 +40,10 @@ #include "checker/ETSchecker.h" #include "checker/ASchecker.h" #include "checker/JSchecker.h" -#include "es2panda.h" -#include "util/declgenEts2Ts.h" #include "checker/ETSAnalyzer.h" #include "checker/TSAnalyzer.h" +#include "public/public.h" +#include "util/declgenEts2Ts.h" #include #include @@ -89,13 +90,26 @@ static CompilerContext::CodeGenCb MakeCompileJob() }; } +static void SetupPublicContext(public_lib::Context *context, const SourceFile *source_file, ArenaAllocator *allocator, + CompileQueue *queue, parser::ParserImpl *parser, CompilerContext *compiler_context) +{ + context->source_file = source_file; + context->allocator = allocator; + context->queue = queue; + context->parser = parser; + context->checker = compiler_context->Checker(); + context->analyzer = context->checker->GetAnalyzer(); + context->compiler_context = compiler_context; + context->emitter = compiler_context->GetEmitter(); +} + using EmitCb = std::function; using PhaseListGetter = std::function()>; template static pandasm::Program *CreateCompiler(const CompilationUnit &unit, const PhaseListGetter &get_phases, - const EmitCb &emit_cb) + CompilerImpl *compiler_impl) { ArenaAllocator allocator(SpaceType::SPACE_TYPE_COMPILER, nullptr, true); auto program = parser::Program::NewProgram(&allocator); @@ -115,12 +129,26 @@ static pandasm::Program *CreateCompiler(const CompilationUnit &unit, const Phase auto emitter = Emitter(&context); context.SetEmitter(&emitter); + public_lib::Context public_context; + SetupPublicContext(&public_context, &unit.input, &allocator, compiler_impl->Queue(), &parser, &context); + auto *es2p_ctx = reinterpret_cast(&public_context); + parser.ParseScript(unit.input, unit.options.compilation_mode == CompilationMode::GEN_STD_LIB); + public_context.state = ES2PANDA_STATE_PARSED; + for (auto &plu : compiler_impl->Plugins()) { + plu.AfterParse(es2p_ctx); + } + if (!checker.StartChecker(binder, unit.options)) { return nullptr; } + public_context.state = ES2PANDA_STATE_CHECKED; + for (auto &plu : compiler_impl->Plugins()) { + plu.AfterCheck(es2p_ctx); + } + if constexpr (std::is_same_v) { if (!unit.options.ts_decl_out.empty() && !util::GenerateTsDeclarations(&checker, &program, unit.options.ts_decl_out)) { @@ -132,39 +160,42 @@ static pandasm::Program *CreateCompiler(const CompilationUnit &unit, const Phase phase->Apply(&context, &program); } + public_context.state = ES2PANDA_STATE_LOWERED; + for (auto &plu : compiler_impl->Plugins()) { + plu.AfterLowerings(es2p_ctx); + } + emitter.GenAnnotation(); - return emit_cb(&context); + return compiler_impl->Emit(&context); } pandasm::Program *CompilerImpl::Compile(const CompilationUnit &unit) { - auto emit_cb = [this](CompilerContext *context) -> pandasm::Program * { return Emit(context); }; - switch (unit.ext) { case ScriptExtension::TS: { return CreateCompiler(unit, compiler::GetEmptyPhaseList, - emit_cb); + this); } case ScriptExtension::AS: { return CreateCompiler(unit, compiler::GetEmptyPhaseList, - emit_cb); + this); } case ScriptExtension::ETS: { return CreateCompiler(unit, compiler::GetETSPhaseList, - emit_cb); + this); } case ScriptExtension::JS: { return CreateCompiler(unit, compiler::GetEmptyPhaseList, - emit_cb); + this); } default: { UNREACHABLE(); diff --git a/compiler/core/compilerImpl.h b/compiler/core/compilerImpl.h index cee0090c0..9ed854e53 100644 --- a/compiler/core/compilerImpl.h +++ b/compiler/core/compilerImpl.h @@ -49,20 +49,35 @@ public: class CompilerImpl { public: - explicit CompilerImpl(size_t thread_count) : queue_(thread_count) {} + explicit CompilerImpl(size_t thread_count, std::vector const *plugins) + : queue_(thread_count), plugins_(plugins) + { + } NO_COPY_SEMANTIC(CompilerImpl); NO_MOVE_SEMANTIC(CompilerImpl); ~CompilerImpl() = default; pandasm::Program *Compile(const CompilationUnit &unit); + std::vector const &Plugins() + { + return *plugins_; + } + static void DumpAsm(const panda::pandasm::Program *prog); -private: panda::pandasm::Program *Emit(CompilerContext *context); + + CompileQueue *Queue() + { + return &queue_; + } + +private: static void HandleContextLiterals(CompilerContext *context); CompileQueue queue_; + std::vector const *plugins_; }; } // namespace panda::es2panda::compiler diff --git a/compiler/lowering/phase.cpp b/compiler/lowering/phase.cpp index a2a6cd89d..ce48b1364 100644 --- a/compiler/lowering/phase.cpp +++ b/compiler/lowering/phase.cpp @@ -38,11 +38,11 @@ std::vector GetETSPhaseList() void Phase::Apply(CompilerContext *ctx, parser::Program *program) { const auto *options = ctx->Options(); - if (options->skip_phases.count(Name()) > 0) { + if (options->skip_phases.count(std::string {Name()}) > 0) { return; } - if (options->dump_before_phases.count(Name()) > 0) { + if (options->dump_before_phases.count(std::string {Name()}) > 0) { std::cout << "Before phase " << Name() << ":" << std::endl; std::cout << program->Dump() << std::endl; } @@ -55,7 +55,7 @@ void Phase::Apply(CompilerContext *ctx, parser::Program *program) Perform(ctx, program); - if (options->dump_after_phases.count(Name()) > 0) { + if (options->dump_after_phases.count(std::string {Name()}) > 0) { std::cout << "After phase " << Name() << ":" << std::endl; std::cout << program->Dump() << std::endl; } diff --git a/es2panda.cpp b/es2panda.cpp index 458f00dd7..7368c9aa8 100644 --- a/es2panda.cpp +++ b/es2panda.cpp @@ -56,10 +56,10 @@ SourceFile::SourceFile(std::string_view fn, std::string_view s, std::string_view { } -Compiler::Compiler(ScriptExtension ext) : Compiler(ext, DEFAULT_THREAD_COUNT) {} +Compiler::Compiler(ScriptExtension ext) : Compiler(ext, DEFAULT_THREAD_COUNT, {}) {} -Compiler::Compiler(ScriptExtension ext, size_t thread_count) - : compiler_(new compiler::CompilerImpl(thread_count)), ext_(ext) +Compiler::Compiler(ScriptExtension ext, size_t thread_count, std::vector &&plugins) + : plugins_(std::move(plugins)), compiler_(new compiler::CompilerImpl(thread_count, &plugins_)), ext_(ext) { } diff --git a/es2panda.h b/es2panda.h index e0bfd9214..2d62c9701 100644 --- a/es2panda.h +++ b/es2panda.h @@ -18,6 +18,7 @@ #include "macros.h" #include "util/arktsconfig.h" +#include "util/plugin.h" #include "util/ustring.h" #include @@ -99,9 +100,10 @@ struct CompilerOptions { bool parse_only {}; std::string std_lib {}; std::string ts_decl_out {}; - std::unordered_set skip_phases {}; - std::unordered_set dump_before_phases {}; - std::unordered_set dump_after_phases {}; + std::vector plugins {}; + std::unordered_set skip_phases {}; + std::unordered_set dump_before_phases {}; + std::unordered_set dump_after_phases {}; std::shared_ptr arkts_config {}; CompilationMode compilation_mode {}; // NOLINTEND(misc-non-private-member-variables-in-classes) @@ -190,7 +192,7 @@ private: class Compiler { public: explicit Compiler(ScriptExtension ext); - explicit Compiler(ScriptExtension ext, size_t thread_count); + explicit Compiler(ScriptExtension ext, size_t thread_count, std::vector &&plugins); ~Compiler(); NO_COPY_SEMANTIC(Compiler); NO_MOVE_SEMANTIC(Compiler); @@ -211,7 +213,13 @@ public: return error_; } + std::vector const &Plugins() + { + return plugins_; + } + private: + std::vector const plugins_; compiler::CompilerImpl *compiler_; Error error_; ScriptExtension ext_; diff --git a/parser/ETSparser.h b/parser/ETSparser.h index 375cf5704..bfd54ce68 100644 --- a/parser/ETSparser.h +++ b/parser/ETSparser.h @@ -37,7 +37,7 @@ public: NO_COPY_SEMANTIC(ETSParser); NO_MOVE_SEMANTIC(ETSParser); - ~ETSParser() = default; + ~ETSParser() final = default; private: struct ImportData { diff --git a/parser/parserImpl.h b/parser/parserImpl.h index e680020b4..dfa15fba7 100644 --- a/parser/parserImpl.h +++ b/parser/parserImpl.h @@ -174,7 +174,7 @@ public: explicit ParserImpl(Program *program, const CompilerOptions &options, ParserStatus status = ParserStatus::NO_OPTS); NO_COPY_SEMANTIC(ParserImpl); NO_MOVE_SEMANTIC(ParserImpl); - ~ParserImpl() = default; + virtual ~ParserImpl() = default; void ParseScript(const SourceFile &source_file, bool gen_std_lib); diff --git a/public/es2panda_lib.cpp b/public/es2panda_lib.cpp index ad9bab55b..701c8b484 100644 --- a/public/es2panda_lib.cpp +++ b/public/es2panda_lib.cpp @@ -1,6 +1,22 @@ +/** + * Copyright (c) 2021-2022 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 "es2panda_lib.h" #include +#include "public/public.h" #include "generated/signatures.h" #include "es2panda.h" #include "assembler/assembly-program.h" @@ -22,39 +38,18 @@ #include "util/options.h" namespace panda::es2panda::public_lib { -struct ConfigImpl { - std::unique_ptr options; -}; - -struct Context { - ConfigImpl *config = nullptr; - std::string source_file_name; - std::string input; - std::unique_ptr source_file; - std::unique_ptr allocator; - std::unique_ptr queue; - - std::unique_ptr parser_program; - std::unique_ptr parser; - std::unique_ptr checker; - std::unique_ptr analyzer; - std::unique_ptr compiler_context; - std::unique_ptr emitter; - std::unique_ptr program; - - es2panda_ContextState state = ES2PANDA_STATE_NEW; - std::string error_message; -}; static char const *StringViewToCString(ArenaAllocator *allocator, util::StringView const sv) { std::string_view utf8 = sv.Utf8(); - if (utf8.data()[utf8.size()] == '\0') { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + if (utf8[utf8.size()] == '\0') { // Avoid superfluous allocation. return utf8.data(); } char *res = reinterpret_cast(allocator->Alloc(utf8.size() + 1)); memmove(res, utf8.cbegin(), utf8.size()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) res[utf8.size()] = '\0'; return res; } @@ -64,6 +59,7 @@ static char const *ArenaStrdup(ArenaAllocator *allocator, char const *src) size_t len = strlen(src); char *res = reinterpret_cast(allocator->Alloc(len + 1)); memmove(res, src, len); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) res[len] = '\0'; return res; } @@ -75,7 +71,7 @@ extern "C" es2panda_Config *CreateConfig(int args, char const **argv) mem::MemConfig::Initialize(0, 0, COMPILER_SIZE, 0, 0, 0); PoolManager::Initialize(PoolType::MMAP); - auto options = std::make_unique(); + auto *options = new util::Options(); if (!options->Parse(args, argv)) { // TODO(gogabr): report option errors properly. std::cerr << options->ErrorMsg() << std::endl; @@ -86,7 +82,7 @@ extern "C" es2panda_Config *CreateConfig(int args, char const **argv) Logger::InitializeStdLogging(Logger::LevelFromString(options->LogLevel()), mask); auto *res = new ConfigImpl; - res->options = std::move(options); + res->options = options; return reinterpret_cast(res); } @@ -95,7 +91,10 @@ extern "C" void DestroyConfig(es2panda_Config *config) PoolManager::Finalize(); mem::MemConfig::Finalize(); - delete reinterpret_cast(config); + auto *cfg = reinterpret_cast(config); + + delete cfg->options; + delete cfg; } static void CompileJob(compiler::CompilerContext *context, binder::FunctionScope *scope, @@ -119,26 +118,26 @@ static es2panda_Context *CreateContext(es2panda_Config *config, std::string cons res->source_file_name = file_name; try { - res->source_file = std::make_unique(res->source_file_name, res->input, cfg->options->ParseModule()); - res->allocator = std::make_unique(SpaceType::SPACE_TYPE_COMPILER, nullptr, true); - res->queue = std::make_unique(cfg->options->ThreadCount()); + res->source_file = new SourceFile(res->source_file_name, res->input, cfg->options->ParseModule()); + res->allocator = new ArenaAllocator(SpaceType::SPACE_TYPE_COMPILER, nullptr, true); + res->queue = new compiler::CompileQueue(cfg->options->ThreadCount()); - auto *binder = res->allocator->New(res->allocator.get()); - res->parser_program = std::make_unique(res->allocator.get(), binder); + auto *binder = res->allocator->New(res->allocator); + res->parser_program = new parser::Program(res->allocator, binder); res->parser_program->MarkEntry(); - res->parser = std::make_unique(res->parser_program.get(), cfg->options->CompilerOptions(), - parser::ParserStatus::NO_OPTS); - res->checker = std::make_unique(); - res->analyzer = std::make_unique(res->checker.get()); - res->checker->SetAnalyzer(res->analyzer.get()); - - binder->SetProgram(res->parser_program.get()); - - res->compiler_context = std::make_unique( - binder, res->checker.get(), cfg->options->CompilerOptions(), CompileJob); - binder->SetCompilerContext(res->compiler_context.get()); - res->emitter = std::make_unique(res->compiler_context.get()); - res->compiler_context->SetEmitter(res->emitter.get()); + res->parser = + new parser::ETSParser(res->parser_program, cfg->options->CompilerOptions(), parser::ParserStatus::NO_OPTS); + res->checker = new checker::ETSChecker(); + res->analyzer = new checker::ETSAnalyzer(res->checker); + res->checker->SetAnalyzer(res->analyzer); + + binder->SetProgram(res->parser_program); + + res->compiler_context = + new compiler::CompilerContext(binder, res->checker, cfg->options->CompilerOptions(), CompileJob); + binder->SetCompilerContext(res->compiler_context); + res->emitter = new compiler::ETSEmitter(res->compiler_context); + res->compiler_context->SetEmitter(res->emitter); res->program = nullptr; res->state = ES2PANDA_STATE_NEW; } catch (Error &e) { @@ -237,7 +236,7 @@ static Context *Lower(Context *ctx) try { for (auto *phase : compiler::GetETSPhaseList()) { - phase->Apply(ctx->compiler_context.get(), ctx->compiler_context->Binder()->Program()); + phase->Apply(ctx->compiler_context, ctx->compiler_context->Binder()->Program()); } ctx->state = ES2PANDA_STATE_LOWERED; @@ -276,13 +275,12 @@ static Context *GenerateAsm(Context *ctx) emitter->LiteralBufferIndex() += ctx->compiler_context->ContextLiterals().size(); /* Main thread can also be used instead of idling */ - ctx->queue->Schedule(ctx->compiler_context.get()); + ctx->queue->Schedule(ctx->compiler_context); ctx->queue->Consume(); ctx->queue->Wait( [emitter](compiler::CompileJob *job) { emitter->AddProgramElement(job->GetProgramElement()); }); ASSERT(ctx->program == nullptr); - ctx->program = std::unique_ptr { - emitter->Finalize(ctx->compiler_context->DumpDebugInfo(), compiler::Signatures::ETS_GLOBAL)}; + ctx->program = emitter->Finalize(ctx->compiler_context->DumpDebugInfo(), compiler::Signatures::ETS_GLOBAL); ctx->state = ES2PANDA_STATE_ASM_GENERATED; } catch (Error &e) { @@ -308,7 +306,7 @@ Context *GenerateBin(Context *ctx) try { ASSERT(ctx->program != nullptr); - util::GenerateProgram(ctx->program.get(), ctx->config->options.get(), + util::GenerateProgram(ctx->program, ctx->config->options, [ctx](const std::string &str) { ctx->error_message = str; }); ctx->state = ES2PANDA_STATE_BIN_GENERATED; @@ -352,8 +350,18 @@ extern "C" es2panda_Context *ProceedToState(es2panda_Context *context, es2panda_ extern "C" void DestroyContext(es2panda_Context *context) { - auto *s = reinterpret_cast(context); - delete s; + auto *ctx = reinterpret_cast(context); + delete ctx->program; + delete ctx->emitter; + delete ctx->compiler_context; + delete ctx->analyzer; + delete ctx->checker; + delete ctx->parser; + delete ctx->parser_program; + delete ctx->queue; + delete ctx->allocator; + delete ctx->source_file; + delete ctx; } extern "C" es2panda_ContextState ContextState(es2panda_Context *context) @@ -415,9 +423,8 @@ extern "C" es2panda_Type *AstNodeType(es2panda_AstNode *ast) // Need to work with other TypeAstNodes if (node->IsExpression()) { return reinterpret_cast(node->AsExpression()->TsType()); - } else { - return nullptr; } + return nullptr; } extern "C" void SetAstNodeType(es2panda_AstNode *ast, es2panda_Type *type) @@ -449,12 +456,12 @@ extern "C" es2panda_AstNode *CreateIdentifier(es2panda_Context *context, char co es2panda_AstNode *type_annotations) { auto *ctx = reinterpret_cast(context); - auto *name_copy = ArenaStrdup(ctx->allocator.get(), name); + auto *name_copy = ArenaStrdup(ctx->allocator, name); auto *tp_ann = reinterpret_cast(type_annotations); ASSERT(tp_ann->IsExpression() && tp_ann->AsExpression()->IsTypeNode()); auto *res = ctx->allocator->New(util::StringView {name_copy}, tp_ann->AsExpression()->AsTypeNode(), - ctx->allocator.get()); + ctx->allocator); return reinterpret_cast(res); } @@ -465,7 +472,7 @@ extern "C" char const *IdentifierName(es2panda_Context *context, es2panda_AstNod auto *id = reinterpret_cast(identifier); ASSERT(id->IsIdentifier()); - return StringViewToCString(ctx->allocator.get(), id->AsIdentifier()->Name()); + return StringViewToCString(ctx->allocator, id->AsIdentifier()->Name()); } extern "C" es2panda_AstNode *IdentifierTypeAnnotation(es2panda_AstNode *identifier) diff --git a/public/es2panda_lib.h b/public/es2panda_lib.h index 09964ea4b..06c928eb4 100644 --- a/public/es2panda_lib.h +++ b/public/es2panda_lib.h @@ -1,15 +1,31 @@ +/** + * Copyright (c) 2021-2022 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. + */ + #ifndef ES2PANDA_LIB #define ES2PANDA_LIB +// Switch off the linter for C header +// NOLINTBEGIN + +#include #include #ifdef __cplusplus extern "C" { #endif -// Switch off the linter for C header -// NOLINTBEGIN - #define ES2PANDA_LIB_VERSION 0 typedef void es2panda_Config; diff --git a/public/public.h b/public/public.h new file mode 100644 index 000000000..9445e100b --- /dev/null +++ b/public/public.h @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2021-2022 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. + */ + +#ifndef ES2PANDA_PUBLIC_PUBLIC_H +#define ES2PANDA_PUBLIC_PUBLIC_H + +#include "public/es2panda_lib.h" + +#include "assembler/assembly-program.h" +#include "libpandabase/mem/arena_allocator.h" + +#include "es2panda.h" +#include "compiler/core/compileQueue.h" +#include "parser/ETSparser.h" +#include "checker/checker.h" +#include "compiler/core/emitter.h" +#include "util/options.h" + +namespace panda::es2panda::public_lib { +struct ConfigImpl { + util::Options *options; +}; + +struct Context { + ConfigImpl *config = nullptr; + std::string source_file_name; + std::string input; + SourceFile const *source_file = nullptr; + ArenaAllocator *allocator = nullptr; + compiler::CompileQueue *queue = nullptr; + + parser::Program *parser_program = nullptr; + parser::ParserImpl *parser = nullptr; + checker::Checker *checker = nullptr; + checker::SemanticAnalyzer *analyzer = nullptr; + compiler::CompilerContext *compiler_context = nullptr; + compiler::Emitter *emitter = nullptr; + pandasm::Program *program = nullptr; + + es2panda_ContextState state = ES2PANDA_STATE_NEW; + std::string error_message; +}; +} // namespace panda::es2panda::public_lib + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f0310c04a..af40cdc21 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -83,7 +83,21 @@ if(PANDA_WITH_ETS) es2panda-public es2panda-lib arkassembler arkbytecodeopt SANITIZERS ${PANDA_SANITIZERS_LIST} + ) + + panda_add_library(e2p_test_plugin SHARED public/e2p_test_plugin.c) + panda_target_include_directories(e2p_test_plugin PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") + panda_target_link_libraries(e2p_test_plugin es2panda-public) + + add_custom_target(es2panda-plugin-test + COMMENT "Test es2panda plugin functionality" + COMMAND $ --plugins=e2p_test_plugin + "${CMAKE_CURRENT_SOURCE_DIR}/public/t.ets" > "${CMAKE_CURRENT_BINARY_DIR}/plugin_test.out" + COMMAND ${CMAKE_COMMAND} -E compare_files + "${CMAKE_CURRENT_BINARY_DIR}/plugin_test.out" "${CMAKE_CURRENT_SOURCE_DIR}/public/plugin_test.expected" ) + add_dependencies(es2panda-plugin-test es2panda e2p_test_plugin) + add_dependencies(es2panda_tests es2panda-plugin-test) add_dependencies(es2panda_tests es2panda-regression-tests) if(TARGET ets_tests) diff --git a/test/public/e2p_test_plugin.c b/test/public/e2p_test_plugin.c new file mode 100644 index 000000000..2de3ca54a --- /dev/null +++ b/test/public/e2p_test_plugin.c @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2023 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. + */ + +// No linting for pure C file +// NOLINTBEGIN + +#include +#include + +#include "public/es2panda_lib.h" + +static struct es2panda_Impl const *impl = NULL; + +void e2p_test_plugin_Initialize() +{ + puts("Hi there!"); + impl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); +} + +static void PrintIfIdentifier(es2panda_AstNode *node, void *arg) +{ + es2panda_Context *ctx = arg; + if (impl->IsIdentifier(node)) { + puts(impl->IdentifierName(ctx, node)); + } +} + +void e2p_test_plugin_AfterParse(es2panda_Context *ctx) +{ + puts("After parse"); + es2panda_AstNode *ast = impl->ProgramAst(impl->ContextProgram(ctx)); + impl->AstNodeForEach(ast, PrintIfIdentifier, ctx); +} + +void e2p_test_plugin_AfterCheck(es2panda_Context *ctx) +{ + puts("After check"); +} + +void e2p_test_plugin_AfterLowerings(es2panda_Context *ctx) +{ + puts("After lowerings"); +} + +// NOLINTEND diff --git a/test/public/es2panda_public_test.cpp b/test/public/es2panda_public_test.cpp index 1015eb8ef..d9cfeeeed 100644 --- a/test/public/es2panda_public_test.cpp +++ b/test/public/es2panda_public_test.cpp @@ -78,8 +78,8 @@ function main() { ASSERT_EQ(impl_->ContextState(ctx), ES2PANDA_STATE_PARSED); struct Arg { - es2panda_Impl const *impl; - es2panda_Context *ctx; + es2panda_Impl const *impl = nullptr; + es2panda_Context *ctx = nullptr; std::vector ids; } arg; arg.impl = impl_; @@ -88,7 +88,7 @@ function main() { auto func = [](es2panda_AstNode *ast, void *argp) { auto *a = reinterpret_cast(argp); if (a->impl->IsIdentifier(ast)) { - a->ids.push_back(a->impl->IdentifierName(a->ctx, ast)); + a->ids.emplace_back(a->impl->IdentifierName(a->ctx, ast)); } }; diff --git a/test/public/plugin_test.expected b/test/public/plugin_test.expected new file mode 100644 index 000000000..f578824e8 --- /dev/null +++ b/test/public/plugin_test.expected @@ -0,0 +1,16 @@ +Hi there! +After parse +ETSGLOBAL +_$init$_ +_$init$_ +main +main +m +n +console +log +m +n + +After check +After lowerings diff --git a/test/public/t.ets b/test/public/t.ets new file mode 100644 index 000000000..5057e93a2 --- /dev/null +++ b/test/public/t.ets @@ -0,0 +1,5 @@ +function main() { + let m: int = 1 + let n: int = 2 + console.log(m + n) +} diff --git a/util/options.cpp b/util/options.cpp index 3547bd5e5..ef9c4d1ef 100644 --- a/util/options.cpp +++ b/util/options.cpp @@ -36,21 +36,31 @@ Options::~Options() delete argparser_; } -static std::unordered_set StringToStringSet(const std::string &str) +static std::vector StringToStringVector(std::string const &str) { - std::unordered_set res; + std::vector res; std::string_view curr_str {str}; auto ix = curr_str.find(','); while (ix != std::string::npos) { if (ix != 0) { - res.insert(curr_str.substr(0, ix)); + res.emplace_back(curr_str.substr(0, ix)); } curr_str = curr_str.substr(ix + 1); ix = curr_str.find(','); } if (!curr_str.empty()) { - res.insert(curr_str); + res.emplace_back(curr_str); + } + return res; +} + +static std::unordered_set StringToStringSet(std::string const &str) +{ + std::vector vec = StringToStringVector(str); + std::unordered_set res; + for (auto &elem : vec) { + res.emplace(elem); } return res; } @@ -85,6 +95,7 @@ bool Options::Parse(int argc, const char **argv) panda::PandArg log_level("log-level", "error", "Log-level"); panda::PandArg std_lib("stdlib", "", "Path to standard library"); panda::PandArg gen_std_lib("gen-stdlib", false, "Gen standard library"); + panda::PandArg plugins("plugins", "", "Plugins"); panda::PandArg skip_phases("skip-phases", "", "Phases to skip"); panda::PandArg dump_before_phases("dump-before-phases", "", "Generate program dump before running phases in the list"); @@ -115,6 +126,7 @@ bool Options::Parse(int argc, const char **argv) argparser_->Add(&log_level); argparser_->Add(&std_lib); argparser_->Add(&gen_std_lib); + argparser_->Add(&plugins); argparser_->Add(&skip_phases); argparser_->Add(&dump_before_phases); argparser_->Add(&dump_after_phases); @@ -297,6 +309,7 @@ bool Options::Parse(int argc, const char **argv) compiler_options_.std_lib = std_lib.GetValue(); compiler_options_.compilation_mode = compilation_mode; compiler_options_.is_ets_module = op_ets_module.GetValue(); + compiler_options_.plugins = StringToStringVector(plugins.GetValue()); compiler_options_.skip_phases = StringToStringSet(skip_phases.GetValue()); compiler_options_.dump_before_phases = StringToStringSet(dump_before_phases.GetValue()); compiler_options_.dump_after_phases = StringToStringSet(dump_after_phases.GetValue()); diff --git a/util/plugin.cpp b/util/plugin.cpp new file mode 100644 index 000000000..cf5203c29 --- /dev/null +++ b/util/plugin.cpp @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021-2022 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 "plugin.h" +#include "os/library_loader.h" + +namespace panda::es2panda::util { + +std::string Plugin::FullNameForProcedure(std::string const &short_name) +{ + return std::string(name_.Utf8()) + "_" + short_name; +} + +Plugin::Plugin(util::StringView const &name) : name_ {name}, ok_ {true}, err_ {0}, h_ {nullptr} +{ + std::string so_name = + os::library_loader::DYNAMIC_LIBRARY_PREFIX + std::string(name) + os::library_loader::DYNAMIC_LIBRARY_SUFFIX; + if (auto load_res = os::library_loader::Load(so_name); load_res.HasValue()) { + h_ = std::move(load_res.Value()); + } else { + err_ = load_res.Error(); + ok_ = false; + } + + if (auto init_res = os::library_loader::ResolveSymbol(h_, FullNameForProcedure("Initialize")); + init_res.HasValue()) { + initialize_ = reinterpret_cast(init_res.Value()); + } + + if (auto ap_res = os::library_loader::ResolveSymbol(h_, FullNameForProcedure("AfterParse")); ap_res.HasValue()) { + after_parse_ = reinterpret_cast(ap_res.Value()); + } + + if (auto ac_res = os::library_loader::ResolveSymbol(h_, FullNameForProcedure("AfterCheck")); ac_res.HasValue()) { + after_check_ = reinterpret_cast(ac_res.Value()); + } + + if (auto al_res = os::library_loader::ResolveSymbol(h_, FullNameForProcedure("AfterLowerings")); + al_res.HasValue()) { + after_lowerings_ = reinterpret_cast(al_res.Value()); + } +} + +} // namespace panda::es2panda::util diff --git a/util/plugin.h b/util/plugin.h new file mode 100644 index 000000000..74fd3c885 --- /dev/null +++ b/util/plugin.h @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2021-2022 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. + */ +#ifndef ES2PANDA_UTIL_PLUGINS_H +#define ES2PANDA_UTIL_PLUGINS_H + +#include "macros.h" +#include "os/library_loader.h" +#include "public/es2panda_lib.h" +#include "util/ustring.h" + +namespace panda::es2panda::util { + +class Plugin { +public: + explicit Plugin(util::StringView const &name); + ~Plugin() = default; + + NO_COPY_SEMANTIC(Plugin); + DEFAULT_MOVE_SEMANTIC(Plugin); + + bool IsOk() + { + return ok_; + } + + os::Error Error() + { + return err_; + } + + void Initialize() const + { + if (initialize_ != nullptr) { + initialize_(); + } + } + + void AfterParse(es2panda_Context *context) const + { + if (after_parse_ != nullptr) { + after_parse_(context); + } + } + + void AfterCheck(es2panda_Context *context) const + { + if (after_check_ != nullptr) { + after_check_(context); + } + } + + void AfterLowerings(es2panda_Context *context) const + { + if (after_lowerings_ != nullptr) { + after_lowerings_(context); + } + } + +private: + std::string FullNameForProcedure(std::string const &short_name); + + util::StringView name_; + bool ok_ {true}; + os::Error err_; + os::library_loader::LibraryHandle h_; + + void (*initialize_)() = nullptr; + void (*after_parse_)(es2panda_Context *) = nullptr; + void (*after_check_)(es2panda_Context *) = nullptr; + void (*after_lowerings_)(es2panda_Context *) = nullptr; +}; + +} // namespace panda::es2panda::util + +#endif -- Gitee