Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

537 lines
17 KiB
C++

/* ************************************************************************
* Copyright (C) 2025-2026 Advanced Micro Devices, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* ************************************************************************ */
/**
* @file IRLexerTest.cpp
* @brief Tests for the IR Lexer (Tokenizer)
*
* This file tests tokenization of assembly IR text into tokens.
*/
#include <gtest/gtest.h>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include "IRLexer.hpp"
using namespace stinkytofu;
/**
* Test fixture for IR Lexer tests
*/
class IRLexerTest : public ::testing::Test {
protected:
void SetUp() override {
// Setup if needed
}
void TearDown() override {
// Cleanup
}
/**
* Helper to tokenize string (filters out EOF and newlines for cleaner tests)
*/
std::vector<Token> tokenizeString(const std::string& input) {
IRLexer lexer(input);
lexer.lex(); // Tokenize the entire input
std::vector<Token> tokens;
for (const auto& token : lexer.getAllTokens()) {
// Filter out EOF and newline tokens for test simplicity
if (token.kind != TokenKind::Eof && token.kind != TokenKind::Newline) {
tokens.push_back(token);
}
}
return tokens;
}
};
// ============================================================================
// BASIC TOKEN TESTS
// ============================================================================
TEST_F(IRLexerTest, TokenizesSimpleIdentifier) {
const std::string input = "v_add_f32";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier);
EXPECT_EQ(tokens[0].text, "v_add_f32");
}
TEST_F(IRLexerTest, TokenizesRegister) {
const std::string input = "v0";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier); // Registers are identifiers
EXPECT_EQ(tokens[0].text, "v0");
}
TEST_F(IRLexerTest, TokenizesScalarRegister) {
const std::string input = "s0";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier); // Scalar registers are identifiers
EXPECT_EQ(tokens[0].text, "s0");
}
TEST_F(IRLexerTest, TokenizesAccumulatorRegister) {
const std::string input = "a0";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier); // Accumulator registers are identifiers
EXPECT_EQ(tokens[0].text, "a0");
}
TEST_F(IRLexerTest, TokenizesIntegerLiteral) {
const std::string input = "42";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::IntegerLiteral);
EXPECT_EQ(tokens[0].text, "42");
}
TEST_F(IRLexerTest, TokenizesFloatLiteral) {
const std::string input = "3.14";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::FloatLiteral);
EXPECT_EQ(tokens[0].text, "3.14");
}
TEST_F(IRLexerTest, TokenizesHexLiteral) {
const std::string input = "0x3f800000";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::HexLiteral);
EXPECT_EQ(tokens[0].text, "0x3f800000");
}
TEST_F(IRLexerTest, TokenizesComma) {
const std::string input = ",";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Comma);
}
TEST_F(IRLexerTest, TokenizesLeftBracket) {
const std::string input = "[";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::LeftBracket);
}
TEST_F(IRLexerTest, TokenizesRightBracket) {
const std::string input = "]";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::RightBracket);
}
TEST_F(IRLexerTest, TokenizesColon) {
const std::string input = ":";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].kind, TokenKind::Colon);
}
// ============================================================================
// COMPLEX TOKEN SEQUENCES
// ============================================================================
TEST_F(IRLexerTest, TokenizesSimpleInstruction) {
const std::string input = "v_add_f32 v0, v1, v2";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 6);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier); // v_add_f32
EXPECT_EQ(tokens[1].kind, TokenKind::Identifier); // v0
EXPECT_EQ(tokens[2].kind, TokenKind::Comma); // ,
EXPECT_EQ(tokens[3].kind, TokenKind::Identifier); // v1
EXPECT_EQ(tokens[4].kind, TokenKind::Comma); // ,
EXPECT_EQ(tokens[5].kind, TokenKind::Identifier); // v2
}
TEST_F(IRLexerTest, TokenizesRegisterRange) {
const std::string input = "v[0:3]";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 6);
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier); // v
EXPECT_EQ(tokens[1].kind, TokenKind::LeftBracket); // [
EXPECT_EQ(tokens[2].kind, TokenKind::IntegerLiteral); // 0
EXPECT_EQ(tokens[3].kind, TokenKind::Colon); // :
EXPECT_EQ(tokens[4].kind, TokenKind::IntegerLiteral); // 3
EXPECT_EQ(tokens[5].kind, TokenKind::RightBracket); // ]
}
TEST_F(IRLexerTest, TokenizesInstructionWithImmediate) {
const std::string input = "v_add_f32 v0, v1, 5.0";
auto tokens = tokenizeString(input);
ASSERT_GE(tokens.size(), 5);
EXPECT_EQ(tokens[tokens.size() - 1].kind, TokenKind::FloatLiteral);
}
// ============================================================================
// WHITESPACE HANDLING
// ============================================================================
TEST_F(IRLexerTest, IgnoresWhitespace) {
const std::string input = "v0 , v1";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 3); // v0, comma, v1 (no whitespace tokens)
}
TEST_F(IRLexerTest, IgnoresNewlines) {
const std::string input = "v0\n,\nv1";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 3);
}
TEST_F(IRLexerTest, IgnoresTabs) {
const std::string input = "v0\t,\tv1";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 3);
}
// ============================================================================
// COMMENT HANDLING
// ============================================================================
TEST_F(IRLexerTest, IgnoresSingleLineComment) {
const std::string input = "v0 // this is a comment";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1); // Only v0, comment ignored
EXPECT_EQ(tokens[0].text, "v0");
}
TEST_F(IRLexerTest, IgnoresBlockComment) {
const std::string input = "v0 /* block comment */ v1";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 2); // v0 and v1
EXPECT_EQ(tokens[0].text, "v0");
EXPECT_EQ(tokens[1].text, "v1");
}
TEST_F(IRLexerTest, HandlesCommentAtStart) {
const std::string input = "// comment\nv0";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].text, "v0");
}
// ============================================================================
// ERROR CASES
// ============================================================================
TEST_F(IRLexerTest, HandlesInvalidCharacters) {
const std::string input = "v0 @ v1"; // @ is likely invalid
auto tokens = tokenizeString(input);
// Should either skip invalid char or mark as error token
// TODO: Check error handling strategy
EXPECT_GE(tokens.size(), 2);
}
TEST_F(IRLexerTest, HandlesUnterminatedString) {
const std::string input = R"(v0 "unterminated)";
auto tokens = tokenizeString(input);
// Should handle gracefully
// TODO: Check error handling
EXPECT_GE(tokens.size(), 1);
}
TEST_F(IRLexerTest, HandlesUnterminatedBlockComment) {
const std::string input = "v0 /* unterminated";
auto tokens = tokenizeString(input);
// Lexer should produce at least the identifier before the comment
ASSERT_GE(tokens.size(), 1) << "Expected at least 1 token (identifier before comment)";
EXPECT_EQ(tokens[0].kind, TokenKind::Identifier);
EXPECT_EQ(tokens[0].text, "v0");
// Current behavior: unterminated block comment is treated as consuming
// the rest of input. Last token may be Identifier or Eof depending on
// how lexer handles the unterminated state.
// Key point: It doesn't crash and produces at least the v0 token
EXPECT_TRUE(tokens.size() >= 1) << "Should handle gracefully without crashing";
}
TEST_F(IRLexerTest, HandlesNumericOverflow) {
// Test extremely large integer that exceeds 64-bit limits
const std::string input = "99999999999999999999999999999999";
auto tokens = tokenizeString(input);
// Lexer should produce a token (either integer, hex, or error)
ASSERT_GE(tokens.size(), 1) << "Should produce at least one token";
// Token should be some numeric type or unknown/error
bool isNumericOrError =
(tokens[0].kind == TokenKind::IntegerLiteral || tokens[0].kind == TokenKind::HexLiteral ||
tokens[0].kind == TokenKind::FloatLiteral || tokens[0].kind == TokenKind::Unknown);
EXPECT_TRUE(isNumericOrError) << "Overflow should produce numeric or error token";
// Key requirement: Doesn't crash, handles overflow gracefully
// Implementation may choose to: clamp value, use arbitrary precision, or mark as error
}
TEST_F(IRLexerTest, HandlesInvalidEscapeSequence) {
// Test string with invalid escape sequence
const std::string input = R"("test\xGG")"; // \xGG is invalid (non-hex after \x)
auto tokens = tokenizeString(input);
// Lexer should produce at least one token
ASSERT_GE(tokens.size(), 1) << "Should produce at least one token";
// Should produce either a QuotedString (accepting invalid escape) or Unknown/Error
bool acceptableKind =
(tokens[0].kind == TokenKind::QuotedString || tokens[0].kind == TokenKind::Unknown);
EXPECT_TRUE(acceptableKind) << "Should produce string or error token";
// Key requirement: Doesn't crash on invalid escape sequences
// Lexer may choose to: accept literally, substitute, or mark as error
}
TEST_F(IRLexerTest, HandlesMalformedScientificNotation) {
// Test scientific notation without exponent digits
const std::string input = "1.23e";
auto tokens = tokenizeString(input);
// Lexer should produce at least one token
ASSERT_GE(tokens.size(), 1) << "Should produce at least one token";
// Current behavior: May lex as "1.23" + "e" (two tokens)
// Or may lex as single malformed float
// Or may produce error token
if (tokens.size() == 1) {
// Single token: either complete float or error
bool acceptableKind =
(tokens[0].kind == TokenKind::FloatLiteral || tokens[0].kind == TokenKind::Unknown);
EXPECT_TRUE(acceptableKind) << "Single token should be float or error";
} else {
// Multiple tokens: "1.23" as float, "e" as identifier
EXPECT_EQ(tokens[0].kind, TokenKind::FloatLiteral) << "First token should be float";
EXPECT_EQ(tokens[1].kind, TokenKind::Identifier) << "Second token should be identifier";
}
// Key requirement: Doesn't crash, produces reasonable token stream
}
TEST_F(IRLexerTest, HandlesExtremelyLongToken) {
// Test DoS protection: Very long identifier (10MB would be too slow for test)
// Use 100K characters instead for reasonable test execution time
std::string input(100000, 'a');
auto tokens = tokenizeString(input);
// Lexer should either:
// 1. Accept the full token (if no length limit)
// 2. Truncate to reasonable length
// 3. Produce error token
ASSERT_GE(tokens.size(), 1) << "Should produce at least one token";
// Should be identifier or error
bool acceptableKind =
(tokens[0].kind == TokenKind::Identifier || tokens[0].kind == TokenKind::Unknown);
EXPECT_TRUE(acceptableKind) << "Long token should be identifier or error";
// Key requirement: Doesn't crash or hang on very long tokens
// Performance requirement: Should complete in reasonable time (< 1 second)
}
// ============================================================================
// EDGE CASES
// ============================================================================
TEST_F(IRLexerTest, HandlesEmptyInput) {
const std::string input = "";
auto tokens = tokenizeString(input);
EXPECT_TRUE(tokens.empty());
}
TEST_F(IRLexerTest, HandlesOnlyWhitespace) {
const std::string input = " \n\t\n ";
auto tokens = tokenizeString(input);
EXPECT_TRUE(tokens.empty());
}
TEST_F(IRLexerTest, HandlesOnlyComments) {
const std::string input = "// comment1\n/* comment2 */";
auto tokens = tokenizeString(input);
EXPECT_TRUE(tokens.empty());
}
TEST_F(IRLexerTest, HandlesVeryLongToken) {
std::string input(10000, 'a'); // 10k character identifier
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 1);
EXPECT_EQ(tokens[0].text.length(), 10000);
}
TEST_F(IRLexerTest, HandlesUnicodeCharacters) {
const std::string input = "???";
auto tokens = tokenizeString(input);
// Unicode characters that aren't recognized as valid tokens
// Lexer should either skip them or mark as unknown/error tokens
// Current behavior: treats '?' as unknown character and continues
EXPECT_GE(tokens.size(), 0) << "Lexer should handle unicode gracefully";
// If tokens are produced, they should not be null/empty
for (const auto& tok : tokens) {
EXPECT_FALSE(tok.text.empty() && tok.kind != TokenKind::Eof);
}
}
TEST_F(IRLexerTest, HandlesNegativeNumbers) {
const std::string input = "-42";
auto tokens = tokenizeString(input);
// Might be tokenized as minus + number, or as negative number
EXPECT_GE(tokens.size(), 1);
}
TEST_F(IRLexerTest, HandlesScientificNotation) {
const std::string input = "1.23e-4";
auto tokens = tokenizeString(input);
// Should recognize as float literal
ASSERT_GE(tokens.size(), 1);
if (tokens[0].kind == TokenKind::FloatLiteral) {
EXPECT_EQ(tokens[0].text, "1.23e-4");
}
}
// ============================================================================
// TOKEN POSITION TRACKING
// ============================================================================
TEST_F(IRLexerTest, TracksLineNumbers) {
const std::string input = "v0\nv1\nv2";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 3);
// TODO: Check line numbers if Token stores them
// EXPECT_EQ(tokens[0].line, 1);
// EXPECT_EQ(tokens[1].line, 2);
// EXPECT_EQ(tokens[2].line, 3);
}
TEST_F(IRLexerTest, TracksColumnNumbers) {
const std::string input = "v0 v1 v2";
auto tokens = tokenizeString(input);
ASSERT_EQ(tokens.size(), 3);
// TODO: Check column numbers if Token stores them
// EXPECT_EQ(tokens[0].column, 1);
// EXPECT_EQ(tokens[1].column, 4);
// EXPECT_EQ(tokens[2].column, 7);
}
// ============================================================================
// PERFORMANCE TESTS
// ============================================================================
TEST_F(IRLexerTest, HandlesLargeInput) {
// Generate large input
std::ostringstream input;
for (int i = 0; i < 10000; ++i) {
input << "v" << i << " ";
}
auto tokens = tokenizeString(input.str());
EXPECT_EQ(tokens.size(), 10000);
}
// ============================================================================
// TODO: Add more tests as lexer implementation evolves
// ============================================================================
// Test all token types
// Test token lookahead (if supported)
// Test token pushback (if supported)
// Test error recovery
// Test lexer state management