// Copyright (C) 2020-2025 Free Software Foundation, Inc.
// This file is part of GCC.
// GCC is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3, or (at your option) any later
// version.
// GCC is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3. If not see
// .
#include "rust-macro-builtins-asm.h"
namespace Rust {
std::map InlineAsmOptionMap{
{AST::InlineAsmOption::PURE, "pure"},
{AST::InlineAsmOption::NOMEM, "nomem"},
{AST::InlineAsmOption::READONLY, "readonly"},
{AST::InlineAsmOption::PRESERVES_FLAGS, "preserves_flags"},
{AST::InlineAsmOption::NORETURN, "noreturn"},
{AST::InlineAsmOption::NOSTACK, "nostack"},
{AST::InlineAsmOption::MAY_UNWIND, "may_unwind"},
{AST::InlineAsmOption::ATT_SYNTAX, "att_syntax"},
{AST::InlineAsmOption::RAW, "raw"},
};
int
parseDirSpec (Parser &parser, TokenId last_token_id)
{
return 0;
}
int
parse_clobber_abi (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
// clobber_abi := "clobber_abi(" *("," ) [","] ")"
// PARSE EVERYTHING COMMITTEDLY IN THIS FUNCTION, WE CONFIRMED VIA clobber_abi
// identifier keyword
auto &inlineAsm = inlineAsmCtx.inlineAsm;
auto token = parser.peek_current_token ();
if (!parser.skip_token (LEFT_PAREN))
{
// TODO: Raise error exactly like rustc if left parenthesis is not
// encountered.
token = parser.peek_current_token ();
// TODO: Error reporting shifted to the left 1 character, I'm not sure
// why.
if (token->get_id () == last_token_id)
{
rust_error_at (parser.peek_current_token ()->get_locus (),
"expected `(`, found end of macro arguments");
return -1;
}
else
{
rust_error_at (
parser.peek_current_token ()->get_locus (),
"expected `(`, found `%s`",
parser.peek_current_token ()->get_token_description ());
}
return -1;
}
if (parser.skip_token (RIGHT_PAREN))
{
// TODO: We encountered a "clobber_abi()", which should be illegal?
// https://github.com/rust-lang/rust/blob/c00957a3e269219413041a4e3565f33b1f9d0779/compiler/rustc_builtin_macros/src/asm.rs#L381
rust_error_at (
parser.peek_current_token ()->get_locus (),
"at least one abi must be provided as an argument to `clobber_abi`");
return -1;
}
std::vector new_abis;
token = parser.peek_current_token ();
while (token->get_id () != last_token_id && token->get_id () != RIGHT_PAREN)
{
// Check if it is a string literal or not, codename: in ABNF
if (token->get_id () == STRING_LITERAL)
{
// TODO: Caring for span in here.
new_abis.push_back ({token->as_string (), token->get_locus ()});
}
else
{
// TODO: We encountered something that is not string literal, which
// should be illegal, please emit the correct error
// https://github.com/rust-lang/rust/blob/b92758a9aef1cef7b79e2b72c3d8ba113e547f89/compiler/rustc_builtin_macros/src/asm.rs#L387
}
if (parser.skip_token (RIGHT_PAREN))
{
break;
}
if (!parser.skip_token (COMMA))
{
// TODO: If the skip of comma is unsuccessful, which should be
// illegal, pleaes emit the correct error.
return -1;
}
token = parser.peek_current_token ();
}
// Done processing the local clobber abis, push that to the main Args in
// argument
for (auto abi : new_abis)
{
inlineAsm.clobber_abi.push_back (abi);
}
return 0;
}
tl::optional
parse_reg (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
using RegType = AST::InlineAsmRegOrRegClass::Type;
if (!parser.skip_token (LEFT_PAREN))
{
// TODO: we expect a left parenthesis here, please return the correct
// error.
return tl::nullopt;
}
// after successful left parenthesis parsing, we should return ast of
// InlineAsmRegOrRegClass of reg or reg class
auto token = parser.peek_current_token ();
auto tok_id = token->get_id ();
AST::InlineAsmRegOrRegClass regClass;
if (parser.skip_token (IDENTIFIER))
{
// construct a InlineAsmRegOrRegClass
regClass.type = RegType::RegClass;
regClass.regClass.Symbol = token->as_string ();
}
else if (tok_id == STRING_LITERAL)
{
// TODO: there is STRING_LITERAL, and BYTE_STRING_LITERAL, should we check
// for both?
// construct a InlineAsmRegOrRegClass
// parse_format_string
regClass.type = RegType::Reg;
inlineAsmCtx.is_explicit = true;
regClass.regClass.Symbol = token->as_string ();
}
else
{
// TODO: This should emit error
// return
// Err(p.dcx().create_err(errors::ExpectedRegisterClassOrExplicitRegister
// {
// span: p.token.span,
// }));
}
if (!parser.skip_token (RIGHT_PAREN))
{
// TODO: we expect a left parenthesis here, please return the correct
// error.
return tl::nullopt;
}
return regClass;
}
int
parse_operand (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
return 0;
}
// From rustc
tl::optional
parse_reg_operand (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
// let name = if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) {
// let (ident, _) = p.token.ident().unwrap();
// p.bump();
// p.expect(&token::Eq)?;
// allow_templates = false;
// Some(ident.name)
// } else {
// None
// };
using RegisterType = AST::InlineAsmOperand::RegisterType;
AST::InlineAsmOperand reg_operand;
auto token = parser.peek_current_token ();
auto iden_token = parser.peek_current_token ();
auto &inlineAsm = inlineAsmCtx.inlineAsm;
if (check_identifier (parser, ""))
{
auto equal_token = parser.peek_current_token ();
if (!parser.skip_token (EQUAL))
{
// TODO: Please handle this.
rust_unreachable ();
}
}
token = parser.peek_current_token ();
bool is_explicit_reg = false;
bool is_global_asm = inlineAsm.is_global_asm;
if (!is_global_asm && check_identifier (parser, "in"))
{
return tl::nullopt;
}
else if (!is_global_asm && check_identifier (parser, "out"))
{
return tl::nullopt;
}
else if (!is_global_asm && check_identifier (parser, "lateout"))
{
return tl::nullopt;
}
else if (!is_global_asm && check_identifier (parser, "inout"))
{
return tl::nullopt;
}
else if (!is_global_asm && check_identifier (parser, "inlateout"))
{
return tl::nullopt;
}
else if (parser.peek_current_token ()->get_id () == CONST)
{
rust_unreachable ();
// todo: Please handle const
return tl::nullopt;
}
else if (false && check_identifier (parser, "sym"))
{
// todo: Please handle sym
return tl::nullopt;
}
else if (false && check_identifier (parser, "label"))
{
// todo: Please handle label
return tl::nullopt;
}
else
{
return tl::nullopt;
}
return reg_operand;
}
void
check_and_set (Parser &parser, InlineAsmContext &inlineAsmCtx,
AST::InlineAsmOption option)
{
auto &inlineAsm = inlineAsmCtx.inlineAsm;
if (inlineAsm.options.count (option) != 0)
{
// TODO: report an error of duplication
rust_error_at (parser.peek_current_token ()->get_locus (),
"the `%s` option was already provided",
InlineAsmOptionMap[option].c_str ());
return;
}
else
{
inlineAsm.options.insert (option);
}
}
int
parse_options (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
bool is_global_asm = inlineAsmCtx.inlineAsm.is_global_asm;
// Parse everything commitedly
if (!parser.skip_token (LEFT_PAREN))
{
// We have shifted `options` to search for the left parenthesis next, we
// should error out if this is not possible.
// TODO: report some error.
return -1;
}
auto token = parser.peek_current_token ();
while (token->get_id () != last_token_id && token->get_id () != RIGHT_PAREN)
{
if (!is_global_asm && check_identifier (parser, "pure"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::PURE);
}
else if (!is_global_asm && check_identifier (parser, "nomem"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::NOMEM);
}
else if (!is_global_asm && check_identifier (parser, "readonly"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::READONLY);
}
else if (!is_global_asm && check_identifier (parser, "preserves_flags"))
{
check_and_set (parser, inlineAsmCtx,
AST::InlineAsmOption::PRESERVES_FLAGS);
}
else if (!is_global_asm && check_identifier (parser, "noreturn"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::NORETURN);
}
else if (!is_global_asm && check_identifier (parser, "nostack"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::NOSTACK);
}
else if (!is_global_asm && check_identifier (parser, "may_unwind"))
{
check_and_set (parser, inlineAsmCtx,
AST::InlineAsmOption::MAY_UNWIND);
}
else if (check_identifier (parser, "att_syntax"))
{
check_and_set (parser, inlineAsmCtx,
AST::InlineAsmOption::ATT_SYNTAX);
}
else if (check_identifier (parser, "raw"))
{
check_and_set (parser, inlineAsmCtx, AST::InlineAsmOption::RAW);
}
else
{
// TODO: Unexpected error, please return the correct error
rust_error_at (token->get_locus (),
"Unexpected token encountered in parse_options");
}
if (parser.skip_token (RIGHT_PAREN))
{
break;
}
// Parse comma as optional
if (parser.skip_token (COMMA))
{
continue;
}
else
{
std::cout << "Sum wrong here, check it out" << std::endl;
token = parser.peek_current_token ();
return -1;
}
}
// TODO: Per rust asm.rs regarding options_spans
// I'm guessing this has to do with some error reporting.
// let new_span = span_start.to(p.prev_token.span);
// args.options_spans.push(new_span);
return 0;
}
bool
check_identifier (Parser &p, std::string ident)
{
auto token = p.peek_current_token ();
if (token->get_id () == IDENTIFIER
&& (token->as_string () == ident || ident == ""))
{
p.skip_token ();
return true;
}
else
{
return false;
}
}
tl::optional
parse_format_string (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
auto token = parser.peek_current_token ();
if (token->get_id () != last_token_id && token->get_id () == STRING_LITERAL)
{
// very nice, we got a supposedly formatted string.
parser.skip_token ();
return token->as_string ();
}
else
{
return tl::nullopt;
}
}
tl::optional
MacroBuiltin::asm_handler (location_t invoc_locus, AST::MacroInvocData &invoc,
bool is_global_asm)
{
return parse_asm (invoc_locus, invoc, is_global_asm);
}
int
parse_asm_arg (Parser &parser, TokenId last_token_id,
InlineAsmContext &inlineAsmCtx)
{
auto token = parser.peek_current_token ();
tl::optional fm_string;
while (token->get_id () != last_token_id)
{
token = parser.peek_current_token ();
// We accept a comma token here.
if (token->get_id () != COMMA
&& inlineAsmCtx.consumed_comma_without_formatted_string)
{
// if it is not a comma, but we consumed it previously, this is fine
// but we have to set it to false tho.
inlineAsmCtx.consumed_comma_without_formatted_string = false;
}
else if (token->get_id () == COMMA
&& !inlineAsmCtx.consumed_comma_without_formatted_string)
{
inlineAsmCtx.consumed_comma_without_formatted_string = false;
parser.skip_token ();
}
else
{
// TODO: we consumed comma, and there happens to also be a comma
// error should be: expected expression, found `,`
break;
}
// And if that token comma is also the trailing comma, we break
// TODO: Check with mentor see what last_token_id means
token = parser.peek_current_token ();
if (token->get_id () == COMMA && token->get_id () == last_token_id)
{
parser.skip_token ();
break;
}
// Ok after the left paren is good, we better be parsing correctly
// everything in here, which is operand in ABNF
// TODO: Parse clobber abi, eat the identifier named "clobber_abi" if true
if (check_identifier (parser, "clobber_abi"))
{
parse_clobber_abi (parser, last_token_id, inlineAsmCtx);
continue;
}
// TODO: Parse options
if (check_identifier (parser, "options"))
{
parse_options (parser, last_token_id, inlineAsmCtx);
continue;
}
// Ok after we have check that neither clobber_abi nor options works, the
// only other logical choice is reg_operand
// std::cout << "reg_operand" << std::endl;
fm_string = parse_format_string (parser, last_token_id, inlineAsmCtx);
}
return 0;
}
tl::optional
parse_asm (location_t invoc_locus, AST::MacroInvocData &invoc,
bool is_global_asm)
{
// From the rule of asm.
// We first peek and see if it is a format string or not.
// If yes, we process the first ever format string, and move on to the
// recurrent of format string Else we exit out
// After that, we peek and see if it is a reoccuring stream of format string
// or not. If it is, keep on going to do this format string. Else, move on
// After that, we peek and see if it is a reoccuring stream of operands or not
// If it is, keep on going to do this operand thingy.
// Else, move on
// We check if there is an optional "," at the end, per ABNF spec.
// If it is, consume it.
// Done
MacroInvocLexer lex (invoc.get_delim_tok_tree ().to_token_stream ());
Parser parser (lex);
auto last_token_id = macro_end_token (invoc.get_delim_tok_tree (), parser);
AST::InlineAsm inlineAsm (invoc_locus, is_global_asm);
auto inlineAsmCtx = InlineAsmContext (inlineAsm);
// Parse the first ever formatted string, success or not, will skip 1 token
auto fm_string = parse_format_string (parser, last_token_id, inlineAsmCtx);
if (fm_string == tl::nullopt)
{
rust_error_at (parser.peek_current_token ()->get_locus (),
"asm template must be a string literal");
return tl::nullopt;
}
// formatted string stream
while (parser.peek_current_token ()->get_id () != last_token_id)
{
if (!parser.skip_token (COMMA))
{
break;
}
// Ok after the comma is good, we better be parsing correctly everything
// in here, which is formatted string in ABNF
inlineAsmCtx.consumed_comma_without_formatted_string = false;
fm_string = parse_format_string (parser, last_token_id, inlineAsmCtx);
if (fm_string == tl::nullopt)
{
inlineAsmCtx.consumed_comma_without_formatted_string = true;
break;
}
}
// operands stream, also handles the optional ","
parse_asm_arg (parser, last_token_id, inlineAsmCtx);
AST::SingleASTNode single
= AST::SingleASTNode (inlineAsmCtx.inlineAsm.clone_expr_without_block ());
std::vector single_vec = {single};
AST::Fragment fragment_ast
= AST::Fragment (single_vec, std::vector> ());
return fragment_ast;
}
} // namespace Rust