diff --git a/.gitignore b/.gitignore index a65d6bb..84c048a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ /build/ -.cache/ -CMakeFiles/ -CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index c0cec04..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -cmake_minimum_required(VERSION 3.21) -project(Elna) - -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(CMAKE_CXX_STANDARD 14) - -find_package(Boost CONFIG COMPONENTS process program_options REQUIRED) -find_package(FLEX REQUIRED) -find_package(BISON REQUIRED) - -FLEX_TARGET(lexer boot/lexer.ll ${CMAKE_CURRENT_BINARY_DIR}/lexer.cc) -BISON_TARGET(parser boot/parser.yy ${CMAKE_CURRENT_BINARY_DIR}/parser.cc) -add_flex_bison_dependency(lexer parser) - -add_library(elna-frontend - boot/ast.cc include/elna/boot/ast.h - include/elna/boot/symbol.h - boot/driver.cc include/elna/boot/driver.h - boot/result.cc include/elna/boot/result.h - ${BISON_parser_OUTPUTS} ${FLEX_lexer_OUTPUTS} -) -target_include_directories(elna-frontend PRIVATE ${CMAKE_CURRENT_BINARY_DIR} include) -target_compile_options(elna-frontend PRIVATE - $<$:-fno-exceptions -fno-rtti> -) - -add_executable(elna tools/main.cc) -target_link_libraries(elna PRIVATE elna-frontend) -target_include_directories(elna PRIVATE ${CMAKE_CURRENT_BINARY_DIR} include ${Boost_INCLUDE_DIR}) -target_link_libraries(elna LINK_PUBLIC ${Boost_LIBRARIES}) -target_compile_options(elna PRIVATE - $<$:-fno-exceptions -fno-rtti> -) diff --git a/Rakefile b/Rakefile index 3560ff1..98596ee 100644 --- a/Rakefile +++ b/Rakefile @@ -1,39 +1,179 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. -} + require 'pathname' +require 'open3' require 'rake/clean' -require_relative 'rakelib/shared' +require_relative 'tools/support' + +# Dependencies. +GCC_VERSION = "14.2.0" +GCC_PATCH = 'https://raw.githubusercontent.com/Homebrew/formula-patches/f30c309442a60cfb926e780eae5d70571f8ab2cb/gcc/gcc-14.2.0-r2.diff' + +# Paths. +HOST_GCC = TMP + 'host/gcc' +HOST_INSTALL = TMP + 'host/install' CLOBBER.include TMP -task :default do - sh 'make -C build' - sh './build/bin/elna' +directory(TMP + 'tools') +directory HOST_GCC +directory HOST_INSTALL + +task default: [TMP + 'elna'] do + sh (TMP + 'elna').to_path, '--tokenize', 'source.elna' end -desc 'Build the bootstrap compiler' -task :boot do -# MacOS: -# --- -# CC=gcc-14 CXX=g++-14 \ -# CFLAGS="-I/opt/homebrew/Cellar/flex/2.6.4_2/include" \ -# CXXFLAGS="-I/opt/homebrew/Cellar/flex/2.6.4_2/include" \ -# ../gcc-14.2.0/configure \ -# --disable-bootstrap \ -# --enable-languages=c,c++,elna \ -# --with-sysroot=/Library/Developer/CommandLineTools/SDKs/MacOSX15.2.sdk \ -# --prefix=$(realpath ../gcc-install) +namespace :boot do + desc 'Download and configure the bootstrap compiler' + task configure: [TMP + 'tools', HOST_GCC, HOST_INSTALL] do + url = URI.parse "https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz" + options = find_build_target GCC_VERSION + source_directory = TMP + "tools/gcc-#{GCC_VERSION}" + frontend_link = source_directory + 'gcc' + + download_and_pipe url, source_directory.dirname, ['tar', '-Jxv'] + download_and_pipe URI.parse(GCC_PATCH), source_directory, ['patch', '-p1'] + + sh 'contrib/download_prerequisites', chdir: source_directory.to_path + File.symlink Pathname.new('.').relative_path_from(frontend_link), (frontend_link + 'elna') + + configure_options = [ + "--prefix=#{HOST_INSTALL.realpath}", + "--with-sysroot=#{options.sysroot.realpath}", + '--enable-languages=c,c++,elna', + '--disable-bootstrap', + '--disable-multilib', + "--target=#{options.build}", + "--build=#{options.build}", + "--host=#{options.build}" + ] + flags = '-O2 -fPIC -I/opt/homebrew/Cellar/flex/2.6.4_2/include' + env = { + 'CC' => options.gcc, + 'CXX' => options.gxx, + 'CFLAGS' => flags, + 'CXXFLAGS' => flags, + } + configure = source_directory.relative_path_from(HOST_GCC) + 'configure' + sh env, configure.to_path, *configure_options, chdir: HOST_GCC.to_path + end + + desc 'Make and install the bootstrap compiler' + task :make do + cwd = HOST_GCC.to_path + + sh 'make', '-j', Etc.nprocessors.to_s, chdir: cwd + sh 'make', 'install', chdir: cwd + end end -desc 'Build cross toolchain' -task :cross, [:target] do |_, args| - args.with_defaults target: 'riscv32-unknown-linux-gnu' - options = find_build_target GCC_VERSION, args[:target] - - options.tools.mkpath - binutils options - gcc1 options - headers options - kernel options - glibc options - gcc2 options - Rake::Task['cross:init'].invoke args[:target] +file (TMP + 'elna').to_path => ['source.elna'] +file (TMP + 'elna').to_path => [(HOST_INSTALL + 'bin/gelna').to_path] do |task| + sh (HOST_INSTALL + 'bin/gelna').to_path, '-o', task.name, task.prerequisites.first +end + +namespace :cross do + desc 'Build cross toolchain' + task :init, [:target] do |_, args| + args.with_defaults target: 'riscv32-unknown-linux-gnu' + + options = find_build_target GCC_VERSION, args[:target] + env = { + 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" + } + sh env, 'riscv32-unknown-linux-gnu-gcc', + '-ffreestanding', '-static', + '-o', (options.tools + 'init').to_path, + 'tools/init.c' + end +end + +namespace :test do + test_sources = FileList['tests/vm/*.elna', 'tests/vm/*.s'] + compiler = TMP + 'bin/elna' + object_directory = TMP + 'riscv/tests' + root_directory = TMP + 'riscv/root' + executable_directory = root_directory + 'tests' + expectation_directory = root_directory + 'expectations' + init = TMP + 'riscv/root/init' + builtin = TMP + 'riscv/builtin.o' + + directory root_directory + directory object_directory + directory executable_directory + directory expectation_directory + + file builtin => ['tools/builtin.s', object_directory] do |task| + sh AS, '-o', task.name, task.prerequisites.first + end + + test_files = test_sources.flat_map do |test_source| + test_basename = File.basename(test_source, '.*') + test_object = object_directory + test_basename.ext('.o') + + file test_object => [test_source, object_directory] do |task| + case File.extname(task.prerequisites.first) + when '.s' + sh AS, '-mno-relax', '-o', task.name, task.prerequisites.first + when '.elna' + sh compiler, '--output', task.name, task.prerequisites.first + else + raise "Unknown source file extension #{task.prerequisites.first}" + end + end + test_executable = executable_directory + test_basename + + file test_executable => [test_object, executable_directory, builtin] do |task| + objects = task.prerequisites.filter { |prerequisite| File.file? prerequisite } + + sh LINKER, '-o', test_executable.to_path, *objects + end + expectation_name = test_basename.ext '.txt' + source_expectation = "tests/expectations/#{expectation_name}" + target_expectation = expectation_directory + expectation_name + + file target_expectation => [source_expectation, expectation_directory] do + cp source_expectation, target_expectation + end + + [test_executable, target_expectation] + end + + file init => [root_directory] do |task| + cp (TMP + 'tools/init'), task.name + end + # Directories should come first. + test_files.unshift executable_directory, expectation_directory, init + + file (TMP + 'riscv/root.cpio') => test_files do |task| + root_files = task.prerequisites + .map { |prerequisite| Pathname.new(prerequisite).relative_path_from(root_directory).to_path } + + File.open task.name, 'wb' do |cpio_file| + cpio_options = { + chdir: root_directory.to_path + } + cpio_stream = Open3.popen2 'cpio', '-o', '--format=newc', cpio_options do |stdin, stdout, wait_thread| + stdin.write root_files.join("\n") + stdin.close + stdout.each { |chunk| cpio_file.write chunk } + wait_thread.value + end + end + end + + task :vm => (TMP + 'riscv/root.cpio') do |task| + kernels = FileList.glob(TMP + 'tools/linux-*/arch/riscv/boot/Image') + + sh 'qemu-system-riscv32', + '-nographic', + '-M', 'virt', + '-bios', 'default', + '-kernel', kernels.first, + '-append', 'quiet panic=1', + '-initrd', task.prerequisites.first, + '-no-reboot' + end end diff --git a/gcc/Make-lang.in b/gcc/Make-lang.in index b1f2cc8..ae1a410 100644 --- a/gcc/Make-lang.in +++ b/gcc/Make-lang.in @@ -42,6 +42,7 @@ elna_OBJS = \ elna/elna-convert.o \ elna/elna-diagnostic.o \ elna/elna-tree.o \ + elna/elna-builtins.o \ elna/ast.o \ elna/driver.o \ elna/lexer.o \ diff --git a/gcc/config-lang.in b/gcc/config-lang.in index bef9588..0cbbe1f 100644 --- a/gcc/config-lang.in +++ b/gcc/config-lang.in @@ -29,7 +29,7 @@ compilers="elna1\$(exeext)" target_libs="" -gtfiles="\$(srcdir)/elna/gcc/elna1.cc" +gtfiles="\$(srcdir)/elna/gcc/elna1.cc \$(srcdir)/elna/include/elna/gcc/elna1.h" lang_requires_boot_languages=c++ diff --git a/gcc/elna-builtins.cc b/gcc/elna-builtins.cc new file mode 100644 index 0000000..0f71e1c --- /dev/null +++ b/gcc/elna-builtins.cc @@ -0,0 +1,31 @@ +#include "elna/gcc/elna-builtins.h" +#include "elna/gcc/elna1.h" +#include "stor-layout.h" +#include "stringpool.h" +#include "elna/gcc/elna-tree.h" + +namespace elna +{ +namespace gcc +{ + void init_ttree() + { + elna_int_type_node = long_integer_type_node; + elna_word_type_node = size_type_node; + elna_char_type_node = unsigned_char_type_node; + elna_bool_type_node = boolean_type_node; + elna_byte_type_node = make_unsigned_type(8); + elna_float_type_node = double_type_node; + + elna_string_type_node = make_node(RECORD_TYPE); + tree_chain record_chain; + + record_chain.append(build_field(UNKNOWN_LOCATION, elna_string_type_node, "length", elna_word_type_node)); + record_chain.append(build_field(UNKNOWN_LOCATION, elna_string_type_node, "ptr", + build_pointer_type_for_mode(elna_char_type_node, VOIDmode, true))); + + TYPE_FIELDS(elna_string_type_node) = record_chain.head(); + layout_type(elna_string_type_node); + } +} +} diff --git a/gcc/elna-diagnostic.cc b/gcc/elna-diagnostic.cc index 2a91267..788a333 100644 --- a/gcc/elna-diagnostic.cc +++ b/gcc/elna-diagnostic.cc @@ -17,6 +17,7 @@ along with GCC; see the file COPYING3. If not see #include "elna/gcc/elna-diagnostic.h" #include "elna/gcc/elna-tree.h" +#include "elna/gcc/elna1.h" namespace elna { @@ -33,23 +34,27 @@ namespace gcc { gcc_assert(TYPE_P(type)); - if (type == integer_type_node) + if (type == elna_int_type_node) { return "Int"; } - else if (type == unsigned_type_node) + else if (type == elna_word_type_node) { return "Word"; } - else if (type == boolean_type_node) + else if (type == elna_bool_type_node) { return "Bool"; } - else if (type == double_type_node) + else if (type == elna_byte_type_node) + { + return "Byte"; + } + else if (type == elna_float_type_node) { return "Float"; } - else if (type == unsigned_char_type_node) + else if (type == elna_char_type_node) { return "Char"; } diff --git a/gcc/elna-generic.cc b/gcc/elna-generic.cc index dc4987d..f130d25 100644 --- a/gcc/elna-generic.cc +++ b/gcc/elna-generic.cc @@ -19,8 +19,10 @@ along with GCC; see the file COPYING3. If not see #include "elna/gcc/elna-generic.h" #include "elna/gcc/elna-diagnostic.h" +#include "elna/gcc/elna1.h" -#include "input.h" +#include "ggc.h" +#include "function.h" #include "cgraph.h" #include "gimplify.h" #include "stringpool.h" @@ -42,7 +44,7 @@ namespace gcc void generic_visitor::visit(boot::call_expression *expression) { - tree symbol = this->symbol_map->lookup(expression->name()); + tree symbol = this->lookup(expression->name()); if (symbol == NULL_TREE) { @@ -91,13 +93,12 @@ namespace gcc { auto body_type = build_type(expression->body()); - this->current_expression = build1(CONVERT_EXPR, this->symbol_map->lookup("Word"), size_in_bytes(body_type)); + this->current_expression = build1(CONVERT_EXPR, elna_word_type_node, size_in_bytes(body_type)); } bool generic_visitor::is_numeric_type(tree type) { - return is_integral_type(type) - || type == this->symbol_map->lookup("Float"); + return is_integral_type(type) || type == elna_float_type_node; } void generic_visitor::visit(boot::program *program) @@ -124,11 +125,15 @@ namespace gcc }; tree declaration_type = build_function_type_array(integer_type_node, 2, parameter_types.data()); tree fndecl = build_fn_decl("main", declaration_type); - current_function_decl = fndecl; + tree resdecl = build_decl(UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE, integer_type_node); DECL_CONTEXT(resdecl) = fndecl; DECL_RESULT(fndecl) = resdecl; + push_struct_function(fndecl, false); + DECL_STRUCT_FUNCTION(fndecl)->language = ggc_cleared_alloc(); + DECL_STRUCT_FUNCTION(fndecl)->language->binding_level = ggc_cleared_alloc(); + enter_scope(); tree_chain argument_chain; @@ -139,7 +144,7 @@ namespace gcc get_identifier(argument_name.c_str()), parameter_types[i]); DECL_CONTEXT(argc_declaration_tree) = fndecl; DECL_ARG_TYPE(argc_declaration_tree) = parameter_types[i]; - this->symbol_map->enter(argument_name, argc_declaration_tree); + argument_chain.append(argc_declaration_tree); } DECL_ARGUMENTS(fndecl) = argument_chain.head(); @@ -161,7 +166,7 @@ namespace gcc DECL_EXTERNAL(fndecl) = 0; DECL_PRESERVE_P(fndecl) = 1; - current_function_decl = NULL_TREE; + pop_cfun(); gimplify_function_tree(fndecl); cgraph_node::finalize_function(fndecl, true); } @@ -260,18 +265,57 @@ namespace gcc return tree_symbol_mapping{ bind_expr, new_block }; } + tree generic_visitor::lookup(const std::string& name) + { + if (name == "Int") + { + return elna_int_type_node; + } + if (name == "Word") + { + return elna_word_type_node; + } + if (name == "Char") + { + return elna_char_type_node; + } + if (name == "Bool") + { + return elna_bool_type_node; + } + if (name == "Byte") + { + return elna_byte_type_node; + } + if (name == "Float") + { + return elna_float_type_node; + } + if (name == "String") + { + return elna_string_type_node; + } + if (current_function_decl != NULL_TREE) + { + for (tree decl = DECL_ARGUMENTS(current_function_decl); decl; decl = DECL_CHAIN(decl)) + { + if (name == IDENTIFIER_POINTER(DECL_NAME(decl))) + { + return decl; + } + } + } + return this->symbol_map->lookup(name); + } + void generic_visitor::visit(boot::number_literal *literal) { - auto symbol = this->symbol_map->lookup("Int"); - - this->current_expression = build_int_cst(symbol, literal->number()); + this->current_expression = build_int_cst(elna_int_type_node, literal->number()); } void generic_visitor::visit(boot::number_literal *literal) { - auto symbol = this->symbol_map->lookup("Word"); - - this->current_expression = build_int_cstu(symbol, literal->number()); + this->current_expression = build_int_cstu(elna_word_type_node, literal->number()); } void generic_visitor::visit(boot::number_literal *literal) @@ -291,16 +335,12 @@ namespace gcc void generic_visitor::visit(boot::number_literal *boolean) { - auto symbol = this->symbol_map->lookup("Bool"); - - this->current_expression = build_int_cst_type(symbol, boolean->number()); + this->current_expression = build_int_cst_type(elna_bool_type_node, boolean->number()); } void generic_visitor::visit(boot::number_literal *character) { - auto symbol = this->symbol_map->lookup("Char"); - - this->current_expression = build_int_cstu(symbol, character->number()); + this->current_expression = build_int_cstu(elna_char_type_node, character->number()); } void generic_visitor::visit(boot::number_literal *) @@ -310,11 +350,8 @@ namespace gcc void generic_visitor::visit(boot::number_literal *string) { - tree index_type = this->symbol_map->lookup("Word"); - tree index_constant = build_int_cstu(index_type, string->number().size()); - tree element_type = this->symbol_map->lookup("Char"); - tree string_type = build_array_type(element_type, build_index_type(index_constant)); - tree string_record = this->symbol_map->lookup("String"); + tree index_constant = build_int_cstu(elna_word_type_node, string->number().size()); + tree string_type = build_array_type(elna_char_type_node, build_index_type(index_constant)); tree string_literal = build_string(string->number().size(), string->number().c_str()); TREE_TYPE(string_literal) = string_type; @@ -322,15 +359,16 @@ namespace gcc TREE_READONLY(string_literal) = 1; TREE_STATIC(string_literal) = 1; - string_type = TREE_TYPE(TREE_CHAIN(TYPE_FIELDS(string_record))); - string_literal = build4(ARRAY_REF, element_type, string_literal, integer_zero_node, NULL_TREE, NULL_TREE); + string_type = TREE_TYPE(TREE_CHAIN(TYPE_FIELDS(elna_string_type_node))); + string_literal = build4(ARRAY_REF, elna_char_type_node, + string_literal, integer_zero_node, NULL_TREE, NULL_TREE); string_literal = build1(ADDR_EXPR, string_type, string_literal); vec *elms = NULL; - CONSTRUCTOR_APPEND_ELT(elms, TYPE_FIELDS(string_record), index_constant); - CONSTRUCTOR_APPEND_ELT(elms, TREE_CHAIN(TYPE_FIELDS(string_record)), string_literal); + CONSTRUCTOR_APPEND_ELT(elms, TYPE_FIELDS(elna_string_type_node), index_constant); + CONSTRUCTOR_APPEND_ELT(elms, TREE_CHAIN(TYPE_FIELDS(elna_string_type_node)), string_literal); - this->current_expression = build_constructor(string_record, elms); + this->current_expression = build_constructor(elna_string_type_node, elms); } tree generic_visitor::build_arithmetic_operation(boot::binary_expression *expression, @@ -344,23 +382,21 @@ namespace gcc tree_code operator_code, tree left, tree right) { return build_binary_operation(is_numeric_type(TREE_TYPE(left)) || is_pointer_type(TREE_TYPE(left)), - expression, operator_code, left, right, this->symbol_map->lookup("Bool")); + expression, operator_code, left, right, elna_bool_type_node); } tree generic_visitor::build_logic_operation(boot::binary_expression *expression, tree_code operator_code, tree left, tree right) { - auto symbol = this->symbol_map->lookup("Bool"); - - return build_binary_operation(TREE_TYPE(left) == symbol, - expression, operator_code, left, right, symbol); + return build_binary_operation(TREE_TYPE(left) == elna_bool_type_node, + expression, operator_code, left, right, elna_bool_type_node); } tree generic_visitor::build_equality_operation(boot::binary_expression *expression, tree_code operator_code, tree left, tree right) { return build_binary_operation(true, expression, - operator_code, left, right, this->symbol_map->lookup("Bool")); + operator_code, left, right, elna_bool_type_node); } void generic_visitor::visit(boot::binary_expression *expression) @@ -388,7 +424,7 @@ namespace gcc } else if (TREE_TYPE(this->current_expression) == ssizetype) { - this->current_expression = fold_convert(this->symbol_map->lookup("Int"), this->current_expression); + this->current_expression = fold_convert(elna_int_type_node, this->current_expression); } return; } @@ -524,7 +560,7 @@ namespace gcc { if (boot::basic_type_expression *basic_type = type.is_basic()) { - tree symbol = this->symbol_map->lookup(basic_type->base_name()); + tree symbol = this->lookup(basic_type->base_name()); if (symbol != NULL_TREE && TYPE_P(symbol)) { @@ -654,7 +690,7 @@ namespace gcc void generic_visitor::visit(boot::variable_expression *expression) { - auto symbol = this->symbol_map->lookup(expression->name()); + auto symbol = this->lookup(expression->name()); if (symbol == NULL_TREE) { diff --git a/gcc/elna-tree.cc b/gcc/elna-tree.cc index 6efdda7..41c6458 100644 --- a/gcc/elna-tree.cc +++ b/gcc/elna-tree.cc @@ -17,11 +17,11 @@ along with GCC; see the file COPYING3. If not see #include "elna/gcc/elna-tree.h" #include "elna/gcc/elna-diagnostic.h" +#include "elna/gcc/elna1.h" #include "stor-layout.h" #include "fold-const.h" #include "diagnostic-core.h" -#include "stringpool.h" namespace elna { @@ -144,33 +144,6 @@ namespace gcc return field_declaration; } - std::shared_ptr> builtin_symbol_table() - { - std::shared_ptr> initial_table = - std::make_shared>(); - - initial_table->enter("Int", long_integer_type_node); - initial_table->enter("Word", size_type_node); - initial_table->enter("Bool", boolean_type_node); - initial_table->enter("Float", double_type_node); - initial_table->enter("Char", unsigned_char_type_node); - initial_table->enter("Byte", make_unsigned_type(8)); - - tree string_record = make_node(RECORD_TYPE); - tree_chain record_chain; - - record_chain.append(build_field(UNKNOWN_LOCATION, string_record, "length", initial_table->lookup("Word"))); - record_chain.append(build_field(UNKNOWN_LOCATION, string_record, "ptr", - build_pointer_type_for_mode(initial_table->lookup("Char"), VOIDmode, true))); - - TYPE_FIELDS(string_record) = record_chain.head(); - layout_type(string_record); - - initial_table->enter("String", string_record); - - return initial_table; - } - tree do_pointer_arithmetic(boot::binary_operator binary_operator, tree left, tree right) { if (binary_operator == boot::binary_operator::sum) diff --git a/gcc/elna1.cc b/gcc/elna1.cc index 57ac521..2ac2a69 100644 --- a/gcc/elna1.cc +++ b/gcc/elna1.cc @@ -19,6 +19,9 @@ along with GCC; see the file COPYING3. If not see #include "system.h" #include "coretypes.h" #include "target.h" +#include "function.h" +#include "tree.h" +#include "elna/gcc/elna1.h" #include "diagnostic.h" #include "opts.h" #include "debug.h" @@ -26,25 +29,14 @@ along with GCC; see the file COPYING3. If not see #include "langhooks-def.h" #include -#include +#include "elna/boot/driver.h" #include "elna/gcc/elna-tree.h" #include "elna/gcc/elna-generic.h" #include "elna/gcc/elna-diagnostic.h" +#include "elna/gcc/elna-builtins.h" #include "parser.hh" -/* Language-dependent contents of a type. */ - -struct GTY (()) lang_type -{ - char dummy; -}; - -/* Language-dependent contents of a decl. */ - -struct GTY (()) lang_decl -{ - char dummy; -}; +tree elna_global_trees[ELNA_TI_MAX]; /* The resulting tree type. */ @@ -56,18 +48,13 @@ union GTY ((desc ("TREE_CODE (&%h.generic) == IDENTIFIER_NODE"), union tree_node GTY ((tag ("0"), desc ("tree_node_structure (&%h)"))) generic; }; -/* We don't use language_function. */ - -struct GTY (()) language_function -{ - int dummy; -}; - /* Language hooks. */ static bool elna_langhook_init(void) { build_common_tree_nodes(false); + elna::gcc::init_ttree(); + build_common_builtin_nodes(); return true; @@ -98,7 +85,7 @@ static void elna_parse_file(const char *filename) } else { - elna::gcc::generic_visitor generic_visitor{ elna::gcc::builtin_symbol_table() }; + elna::gcc::generic_visitor generic_visitor{ std::make_shared>() }; generic_visitor.visit(driver.tree.get()); } @@ -185,7 +172,26 @@ static bool global_bindings_p(void) return current_function_decl == NULL_TREE; } -static tree pushdecl(tree decl) +tree getdecls(void) +{ + if (current_function_decl != NULL_TREE) + { + return f_binding_level->names; + } + return NULL_TREE; +} + +tree pushdecl(tree decl) +{ + if (current_function_decl != NULL_TREE) + { + TREE_CHAIN(decl) = f_binding_level->names; + f_binding_level->names = decl; + } + return decl; +} + +static tree elna_langhook_builtin_function(tree decl) { return decl; } @@ -203,10 +209,7 @@ static tree pushdecl(tree decl) #define LANG_HOOKS_TYPE_FOR_MODE elna_langhook_type_for_mode #undef LANG_HOOKS_BUILTIN_FUNCTION -#define LANG_HOOKS_BUILTIN_FUNCTION pushdecl - -#undef LANG_HOOKS_GETDECLS -#define LANG_HOOKS_GETDECLS hook_tree_void_null +#define LANG_HOOKS_BUILTIN_FUNCTION elna_langhook_builtin_function #undef LANG_HOOKS_IDENTIFIER_SIZE #define LANG_HOOKS_IDENTIFIER_SIZE sizeof(struct tree_identifier) diff --git a/include/elna/gcc/elna-builtins.h b/include/elna/gcc/elna-builtins.h new file mode 100644 index 0000000..83e3cfe --- /dev/null +++ b/include/elna/gcc/elna-builtins.h @@ -0,0 +1,13 @@ +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "tree.h" +#include "tree-iterator.h" + +namespace elna +{ +namespace gcc +{ + void init_ttree(); +} +} diff --git a/include/elna/gcc/elna-generic.h b/include/elna/gcc/elna-generic.h index b61aa68..fe74cab 100644 --- a/include/elna/gcc/elna-generic.h +++ b/include/elna/gcc/elna-generic.h @@ -46,6 +46,8 @@ namespace gcc void enter_scope(); tree_symbol_mapping leave_scope(); + tree lookup(const std::string& name); + void make_if_branch(boot::conditional_statements& branch, tree goto_endif); bool is_numeric_type(tree type); diff --git a/include/elna/gcc/elna-tree.h b/include/elna/gcc/elna-tree.h index da62da3..cb09caf 100644 --- a/include/elna/gcc/elna-tree.h +++ b/include/elna/gcc/elna-tree.h @@ -24,6 +24,7 @@ along with GCC; see the file COPYING3. If not see #include "coretypes.h" #include "tree.h" #include "tree-iterator.h" +#include "stringpool.h" #include "elna/boot/ast.h" #include "elna/boot/symbol.h" @@ -96,8 +97,6 @@ namespace gcc tree chain_defer(); }; - std::shared_ptr> builtin_symbol_table(); - tree do_pointer_arithmetic(boot::binary_operator binary_operator, tree left, tree right); tree build_binary_operation(bool condition, boot::binary_expression *expression, tree_code operator_code, tree left, tree right, tree target_type); diff --git a/include/elna/gcc/elna1.h b/include/elna/gcc/elna1.h new file mode 100644 index 0000000..b43dbac --- /dev/null +++ b/include/elna/gcc/elna1.h @@ -0,0 +1,47 @@ +enum elna_tree_index +{ + ELNA_TI_INT_TYPE, + ELNA_TI_WORD_TYPE, + ELNA_TI_CHAR_TYPE, + ELNA_TI_BOOL_TYPE, + ELNA_TI_BYTE_TYPE, + ELNA_TI_FLOAT_TYPE, + ELNA_TI_STRING_TYPE, + ELNA_TI_MAX +}; + +extern GTY(()) tree elna_global_trees[ELNA_TI_MAX]; + +#define elna_int_type_node elna_global_trees[ELNA_TI_INT_TYPE] +#define elna_word_type_node elna_global_trees[ELNA_TI_WORD_TYPE] +#define elna_char_type_node elna_global_trees[ELNA_TI_CHAR_TYPE] +#define elna_bool_type_node elna_global_trees[ELNA_TI_BOOL_TYPE] +#define elna_byte_type_node elna_global_trees[ELNA_TI_BYTE_TYPE] +#define elna_float_type_node elna_global_trees[ELNA_TI_FLOAT_TYPE] +#define elna_string_type_node elna_global_trees[ELNA_TI_STRING_TYPE] + +/* Language-dependent contents of a type. */ +struct GTY (()) lang_type +{ +}; + +/* Language-dependent contents of a decl. */ +struct GTY (()) lang_decl +{ +}; + +struct GTY (()) binding_level +{ + // A chain of all declarations in this binding level. + tree names; +}; + +struct GTY (()) language_function +{ + struct binding_level *binding_level; +}; + +#define f_binding_level DECL_STRUCT_FUNCTION(current_function_decl)->language->binding_level + +extern tree pushdecl(tree); +extern tree getdecls(void); diff --git a/rakelib/cross.rake b/rakelib/cross.rake deleted file mode 100644 index 3889c02..0000000 --- a/rakelib/cross.rake +++ /dev/null @@ -1,245 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public License, -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at https://mozilla.org/MPL/2.0/. -} - -require 'uri' -require 'net/http' -require 'open3' -require 'etc' -require 'fileutils' -require_relative 'shared' - -BINUTILS_VERSION = '2.43.1' -GLIBC_VERSION = '2.40' -KERNEL_VERSION = '5.15.166' - -def download_and_unarchive(url, target) - case File.extname url.path - when '.bz2' - archive_type = '-j' - root_directory = File.basename url.path, '.tar.bz2' - when '.xz' - archive_type = '-J' - root_directory = File.basename url.path, '.tar.xz' - else - raise "Unsupported archive type #{url.path}." - end - - Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| - request = Net::HTTP::Get.new url.request_uri - - http.request request do |response| - case response - when Net::HTTPRedirection - download_and_unarchive URI.parse(response['location']) - when Net::HTTPSuccess - Open3.popen2 'tar', '-C', target.to_path, archive_type, '-xv' do |stdin, stdout, wait_thread| - Thread.new do - stdout.each { |line| puts line } - end - - response.read_body do |chunk| - stdin.write chunk - end - stdin.close - - wait_thread.value - end - else - response.error! - end - end - end - target + root_directory -end - -def configure_make_install(source_directory, configure_options, env, cwd) - configure = source_directory.relative_path_from(cwd) + 'configure' - system env, configure.to_path, *configure_options, chdir: cwd.to_path, exception: true - system 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true - system env, 'make', 'install', chdir: cwd.to_path, exception: true -end - -# Build cross binutils. -def binutils(options) - source_directory = download_and_unarchive( - URI.parse("https://ftp.gnu.org/gnu/binutils/binutils-#{BINUTILS_VERSION}.tar.xz"), - options.tools) - - cwd = source_directory.dirname + 'build-binutils' - cwd.mkpath - options.rootfs.mkpath - - env = { - 'CC' => options.gcc, - 'CXX' => options.gxx - } - configure_options = [ - "--prefix=#{options.rootfs.realpath}", - "--target=#{options.target}", - '--disable-nls', - '--enable-gprofng=no', - '--disable-werror', - '--enable-default-hash-style=gnu', - '--disable-libquadmath' - ] - configure_make_install source_directory, configure_options, env, cwd -end - -# Build stage 1 GCC. -def gcc1(options) - source_directory = download_and_unarchive( - URI.parse("https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz"), - options.tools) - - cwd = source_directory.dirname + 'build-gcc' - cwd.mkpath - options.rootfs.mkpath - options.sysroot.mkpath - - system 'contrib/download_prerequisites', chdir: source_directory.to_path, exception: true - configure_options = options.configuration + [ - "--prefix=#{options.rootfs.realpath}", - "--with-sysroot=#{options.sysroot.realpath}", - '--enable-languages=c,c++', - '--disable-shared', - '--disable-bootstrap', - '--disable-multilib', - '--disable-libmudflap', - '--disable-libssp', - '--disable-libquadmath', - '--disable-libsanitizer', - '--disable-threads', - '--disable-libatomic', - '--disable-libgomp', - '--disable-libvtv', - '--disable-libstdcxx', - '--disable-nls', - '--with-newlib', - '--without-headers', - "--target=#{options.target}", - "--build=#{options.build}", - "--host=#{options.build}" - ] - flags = '-O2 -fPIC' - env = { - 'CC' => options.gcc, - 'CXX' => options.gxx, - 'CFLAGS' => flags, - 'CXXFLAGS' => flags, - 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" - } - configure_make_install source_directory, configure_options, env, cwd -end - -# Copy glibc headers. -def headers(options) - source_directory = download_and_unarchive( - URI.parse("https://ftp.gnu.org/gnu/glibc/glibc-#{GLIBC_VERSION}.tar.xz"), - options.tools) - include_directory = options.tools + 'include' - - include_directory.mkpath - FileUtils.cp (source_directory + 'elf/elf.h'), (include_directory + 'elf.h') -end - -# Build linux kernel. -def kernel(options) - cwd = download_and_unarchive( - URI.parse("https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-#{KERNEL_VERSION}.tar.xz"), - options.tools) - - env = { - 'CROSS_COMPILE' => "#{options.target}-", - 'ARCH' => 'riscv', - 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}", - 'HOSTCFLAGS' => "-D_UUID_T -D__GETHOSTUUID_H -I#{options.tools.realpath + 'include'}" - } - system env, 'make', 'rv32_defconfig', chdir: cwd.to_path, exception: true - system env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true - system env, 'make', 'headers', chdir: cwd.to_path, exception: true - - user_directory = options.sysroot + 'usr' - - user_directory.mkpath - FileUtils.cp_r (cwd + 'usr/include'), (user_directory + 'include') -end - -# Build glibc. -def glibc(options) - source_directory = options.tools + "glibc-#{GLIBC_VERSION}" - configure_options = [ - '--prefix=/usr', - "--host=#{options.target}", - "--target=#{options.target}", - "--build=#{options.build}", - "--enable-kernel=#{KERNEL_VERSION}", - "--with-headers=#{options.sysroot.realpath + 'usr/include'}", - '--disable-nscd', - '--disable-libquadmath', - '--disable-libitm', - '--disable-werror', - 'libc_cv_forced_unwind=yes' - ] - bin = options.rootfs.realpath + 'bin' - env = { - 'PATH' => "#{bin}:#{ENV['PATH']}", - 'MAKE' => 'make' # Otherwise it uses gnumake which can be different and too old. - } - cwd = source_directory.dirname + 'build-glibc' - cwd.mkpath - - configure = source_directory.relative_path_from(cwd) +'./configure' - system env, configure.to_path, *configure_options, chdir: cwd.to_path, exception: true - system env, 'make', '-j', Etc.nprocessors.to_s, chdir: cwd.to_path, exception: true - system env, 'make', "install_root=#{options.sysroot.realpath}", 'install', chdir: cwd.to_path, exception: true -end - -# Build stage 2 GCC. -def gcc2(options) - source_directory = options.tools + "gcc-#{GCC_VERSION}" - cwd = options.tools + 'build-gcc' - - FileUtils.rm_rf cwd - cwd.mkpath - - configure_options = options.configuration + [ - "--prefix=#{options.rootfs.realpath}", - "--with-sysroot=#{options.sysroot.realpath}", - '--enable-languages=c,c++,lto', - '--enable-lto', - '--enable-shared', - '--disable-bootstrap', - '--disable-multilib', - '--enable-checking=release', - '--disable-libssp', - '--disable-libquadmath', - '--enable-threads=posix', - '--with-default-libstdcxx-abi=new', - '--disable-nls', - "--target=#{options.target}", - "--build=#{options.build}", - "--host=#{options.build}" - - ] - flags = '-O2 -fPIC' - env = { - 'CFLAGS' => flags, - 'CXXFLAGS' => flags, - 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" - } - configure_make_install source_directory, configure_options, env, cwd -end - -namespace :cross do - task :init, [:target] do |_, args| - options = find_build_target GCC_VERSION, args[:target] - env = { - 'PATH' => "#{options.rootfs.realpath + 'bin'}:#{ENV['PATH']}" - } - sh env, 'riscv32-unknown-linux-gnu-gcc', - '-ffreestanding', '-static', - '-o', (options.tools + 'init').to_path, - 'tools/init.c' - end -end diff --git a/rakelib/shared.rb b/rakelib/shared.rb deleted file mode 100644 index c0b59b6..0000000 --- a/rakelib/shared.rb +++ /dev/null @@ -1,72 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public License, -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at https://mozilla.org/MPL/2.0/. -} - -GCC_VERSION = "14.2.0" - -TMP = Pathname.new('./build') - -class BuildTarget - attr_accessor(:build, :gcc, :target, :tmp) - - def gxx - @gcc.gsub 'c', '+' - end - - def sysroot - tmp + 'sysroot' - end - - def rootfs - tmp + 'rootfs' - end - - def tools - tmp + 'tools' - end - - def configuration - case target - when /^riscv[[:digit:]]+-/ - [ - '--with-arch=rv32imafdc', - '--with-abi=ilp32d', - '--with-tune=rocket', - '--with-isa-spec=20191213' - ] - else - [] - end - end -end - -def gcc_verbose(gcc_binary) - read, write = IO.pipe - sh({'LANG' => 'C'}, gcc_binary, '--verbose', err: write) - write.close - output = read.read - read.close - output -end - -def find_build_target(gcc_version, target) - gcc_binary = 'gcc' - output = gcc_verbose gcc_binary - - if output.start_with? 'Apple clang' - gcc_binary = "gcc-#{gcc_version.split('.').first}" - output = gcc_verbose gcc_binary - end - result = output - .lines - .each_with_object(BuildTarget.new) do |line, accumulator| - if line.start_with? 'Target: ' - accumulator.build = line.split(' ').last.strip - elsif line.start_with? 'COLLECT_GCC' - accumulator.gcc = line.split('=').last.strip - end - end - result.tmp = TMP - result.target = target - result -end diff --git a/rakelib/tester.rake b/rakelib/tester.rake deleted file mode 100644 index 4fa5b8c..0000000 --- a/rakelib/tester.rake +++ /dev/null @@ -1,100 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public License, -# v. 2.0. If a copy of the MPL was not distributed with this file, You can -# obtain one at https://mozilla.org/MPL/2.0/. -} - -require 'open3' -require 'rake/clean' -require_relative 'shared' - -CLEAN.include(TMP + 'riscv') - -LINKER = 'build/rootfs/riscv32-unknown-linux-gnu/bin/ld' -AS = 'build/rootfs/riscv32-unknown-linux-gnu/bin/as' - -namespace :test do - test_sources = FileList['tests/vm/*.elna', 'tests/vm/*.s'] - compiler = TMP + 'bin/elna' - object_directory = TMP + 'riscv/tests' - root_directory = TMP + 'riscv/root' - executable_directory = root_directory + 'tests' - expectation_directory = root_directory + 'expectations' - init = TMP + 'riscv/root/init' - builtin = TMP + 'riscv/builtin.o' - - directory root_directory - directory object_directory - directory executable_directory - directory expectation_directory - - file builtin => ['tools/builtin.s', object_directory] do |task| - sh AS, '-o', task.name, task.prerequisites.first - end - - test_files = test_sources.flat_map do |test_source| - test_basename = File.basename(test_source, '.*') - test_object = object_directory + test_basename.ext('.o') - - file test_object => [test_source, object_directory] do |task| - case File.extname(task.prerequisites.first) - when '.s' - sh AS, '-mno-relax', '-o', task.name, task.prerequisites.first - when '.elna' - sh compiler, '--output', task.name, task.prerequisites.first - else - raise "Unknown source file extension #{task.prerequisites.first}" - end - end - test_executable = executable_directory + test_basename - - file test_executable => [test_object, executable_directory, builtin] do |task| - objects = task.prerequisites.filter { |prerequisite| File.file? prerequisite } - - sh LINKER, '-o', test_executable.to_path, *objects - end - expectation_name = test_basename.ext '.txt' - source_expectation = "tests/expectations/#{expectation_name}" - target_expectation = expectation_directory + expectation_name - - file target_expectation => [source_expectation, expectation_directory] do - cp source_expectation, target_expectation - end - - [test_executable, target_expectation] - end - - file init => [root_directory] do |task| - cp (TMP + 'tools/init'), task.name - end - # Directories should come first. - test_files.unshift executable_directory, expectation_directory, init - - file (TMP + 'riscv/root.cpio') => test_files do |task| - root_files = task.prerequisites - .map { |prerequisite| Pathname.new(prerequisite).relative_path_from(root_directory).to_path } - - File.open task.name, 'wb' do |cpio_file| - cpio_options = { - chdir: root_directory.to_path - } - cpio_stream = Open3.popen2 'cpio', '-o', '--format=newc', cpio_options do |stdin, stdout, wait_thread| - stdin.write root_files.join("\n") - stdin.close - stdout.each { |chunk| cpio_file.write chunk } - wait_thread.value - end - end - end - - task :vm => (TMP + 'riscv/root.cpio') do |task| - kernels = FileList.glob(TMP + 'tools/linux-*/arch/riscv/boot/Image') - - sh 'qemu-system-riscv32', - '-nographic', - '-M', 'virt', - '-bios', 'default', - '-kernel', kernels.first, - '-append', 'quiet panic=1', - '-initrd', task.prerequisites.first, - '-no-reboot' - end -end diff --git a/tools/main.cc b/tools/main.cc deleted file mode 100644 index cea8790..0000000 --- a/tools/main.cc +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include "parser.hh" -#include - -constexpr std::size_t pointer_size = 4; - -int main() -{ - elna::source::driver driver{ "-" }; - std::istringstream inp(R"( - proc f(); - begin - end; - - var x: Int; - - begin - x := 4 + 2 - end. - )"); - - elna::source::lexer lexer(inp); - yy::parser parser(lexer, driver); - - if (auto result = parser()) - { - for (const auto& error : driver.errors()) - { - std::cerr << error->path << ':' - << error->line() << ':' << error->column() - << ": error: " << error->what() - << '.' << std::endl; - } - return result; - } - for (auto& definition : driver.tree->definitions()) - { - std::cout << "Definition identifier: " << definition->identifier() << std::endl; - } - return 0; -} diff --git a/tools/support.rb b/tools/support.rb new file mode 100644 index 0000000..945580a --- /dev/null +++ b/tools/support.rb @@ -0,0 +1,92 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. -} + +require 'uri' +require 'net/http' +require 'open3' + +LINKER = 'build/rootfs/riscv32-unknown-linux-gnu/bin/ld' +AS = 'build/rootfs/riscv32-unknown-linux-gnu/bin/as' + +TMP = Pathname.new('./build') + +class BuildTarget + attr_accessor(:build, :gcc, :sysroot, :tmp) + + def initialize + @sysroot = '/' + end + + def gxx + @gcc.gsub 'c', '+' + end + + def rootfs + tmp + 'rootfs' + end +end + +def gcc_verbose(gcc_binary) + read, write = IO.pipe + sh({'LANG' => 'C'}, gcc_binary, '--verbose', err: write) + write.close + output = read.read + read.close + output +end + +def find_build_target(gcc_version) + gcc_binary = 'gcc' + output = gcc_verbose gcc_binary + + if output.start_with? 'Apple clang' + gcc_binary = "gcc-#{gcc_version.split('.').first}" + output = gcc_verbose gcc_binary + sdk = Pathname.new '/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk' + end + result = output + .lines + .each_with_object(BuildTarget.new) do |line, accumulator| + if line.start_with? 'Target: ' + accumulator.build = line.split(' ').last.strip + elsif line.start_with? 'COLLECT_GCC' + accumulator.gcc = line.split('=').last.strip + end + end + result.tmp = TMP + result.sysroot = sdk + result +end + +def download_and_pipe(url, target, command) + target.mkpath + + Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| + request = Net::HTTP::Get.new url.request_uri + + http.request request do |response| + case response + when Net::HTTPRedirection + download_and_pipe URI.parse(response['location']), target, command + when Net::HTTPSuccess + Dir.chdir target.to_path do + Open3.popen2(*command) do |stdin, stdout, wait_thread| + Thread.new do + stdout.each { |line| puts line } + end + + response.read_body do |chunk| + stdin.write chunk + end + stdin.close + + wait_thread.value + end + end + else + response.error! + end + end + end +end