Task 582: .PTX File Format

Task 582: .PTX File Format

The .PTX file format is NVIDIA's Parallel Thread Execution (PTX), a text-based pseudo-assembly language and virtual machine model serving as an intermediate representation for CUDA programs targeting NVIDIA GPUs. It defines a low-level ISA that is compiled just-in-time (JIT) to native GPU instructions (SASS) at runtime. PTX files are human-readable ASCII text with an assembly-style syntax, consisting of directives, statements, instructions, and optional comments. Each PTX module must start with a .version directive followed by a .target directive. The full specification is detailed in the official NVIDIA PTX ISA documentation.

Based on the PTX specification, the key properties intrinsic to the format (extractable from the structure of any valid .ptx file) are:

  • Version: The PTX ISA version (e.g., 9.0), specified via the .version directive.
  • Target: The target GPU architecture (e.g., sm_90), specified via the .target directive.
  • Address Size: The pointer size (32 or 64 bits), specified via the .address_size directive (defaults to 64 if omitted).
  • Entry Points: List of kernel entry point names, defined via .entry directives.
  • Functions: List of device function names, defined via .func directives.
  • Variables: List of variable declarations in state spaces like .global, .const, .shared (including type, name, and optional initializers/alignments).
  • Registers: List of register declarations via .reg (including type and names or parameterized sets).
    These properties define the core structure, with additional elements like pragmas (.pragma), max constraints (.maxnreg, .maxntid), and instructions building on them.

Two direct download links for example .ptx files:

Here is embeddable HTML+JavaScript code for a Ghost blog post (or any HTML-based blog). It creates a drag-and-drop area where a user can drop a .ptx file, parses it client-side to extract the properties from the list above, and dumps them to the screen in a readable format. Embed this in your blog post via a custom HTML block.

Drag and drop a .PTX file here
  1. Python class:
import re

class PTXParser:
    def __init__(self):
        self.version = None
        self.target = None
        self.address_size = None
        self.entries = []
        self.funcs = []
        self.variables = []
        self.registers = []
        self.text = None

    def open(self, filepath):
        with open(filepath, 'r') as f:
            self.text = f.read()
        self.decode()

    def decode(self):
        if not self.text:
            raise ValueError("No file loaded")
        lines = self.text.splitlines()
        for line in lines:
            line = line.strip()
            if not line or line.startswith('//'):
                continue
            if line.startswith('.version'):
                self.version = line.split()[1]
            elif line.startswith('.target'):
                self.target = line.split()[1]
            elif line.startswith('.address_size'):
                self.address_size = line.split()[1]
            elif line.startswith('.entry'):
                name = re.split(r'\s+|\(', line)[1]
                self.entries.append(name)
            elif line.startswith('.func'):
                name = re.split(r'\s+|\(', line)[1]
                self.funcs.append(name)
            elif line.startswith('.global') or line.startswith('.const') or line.startswith('.shared'):
                self.variables.append(line)
            elif line.startswith('.reg'):
                self.registers.append(line)

    def print_properties(self):
        print(f"Version: {self.version or 'Not found'}")
        print(f"Target: {self.target or 'Not found'}")
        print(f"Address Size: {self.address_size or '64 (default)'}")
        print("Entry Points:", ', '.join(self.entries) if self.entries else 'None')
        print("Functions:", ', '.join(self.funcs) if self.funcs else 'None')
        print("Variables:", '\n'.join(self.variables) if self.variables else 'None')
        print("Registers:", '\n'.join(self.registers) if self.registers else 'None')

    def write(self, filepath, modified_text=None):
        text_to_write = modified_text if modified_text else self.text
        with open(filepath, 'w') as f:
            f.write(text_to_write)

# Example usage:
# parser = PTXParser()
# parser.open('example.ptx')
# parser.print_properties()
# parser.write('output.ptx')
  1. Java class:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PTXParser {
    private String version;
    private String target;
    private String addressSize;
    private List<String> entries = new ArrayList<>();
    private List<String> funcs = new ArrayList<>();
    private List<String> variables = new ArrayList<>();
    private List<String> registers = new ArrayList<>();
    private String text;

    public void open(String filepath) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
        }
        this.text = sb.toString();
        decode();
    }

    public void decode() {
        if (text == null) {
            throw new IllegalStateException("No file loaded");
        }
        String[] lines = text.split("\n");
        for (String line : lines) {
            line = line.trim();
            if (line.isEmpty() || line.startsWith("//")) {
                continue;
            }
            String[] parts = line.split("\\s+");
            if (line.startsWith(".version")) {
                version = parts[1];
            } else if (line.startsWith(".target")) {
                target = parts[1];
            } else if (line.startsWith(".address_size")) {
                addressSize = parts[1];
            } else if (line.startsWith(".entry")) {
                String name = parts[1].split("\\(")[0];
                entries.add(name);
            } else if (line.startsWith(".func")) {
                String name = parts[1].split("\\(")[0];
                funcs.add(name);
            } else if (line.startsWith(".global") || line.startsWith(".const") || line.startsWith(".shared")) {
                variables.add(line);
            } else if (line.startsWith(".reg")) {
                registers.add(line);
            }
        }
    }

    public void printProperties() {
        System.out.println("Version: " + (version != null ? version : "Not found"));
        System.out.println("Target: " + (target != null ? target : "Not found"));
        System.out.println("Address Size: " + (addressSize != null ? addressSize : "64 (default)"));
        System.out.println("Entry Points: " + (entries.isEmpty() ? "None" : String.join(", ", entries)));
        System.out.println("Functions: " + (funcs.isEmpty() ? "None" : String.join(", ", funcs)));
        System.out.println("Variables: " + (variables.isEmpty() ? "None" : String.join("\n", variables)));
        System.out.println("Registers: " + (registers.isEmpty() ? "None" : String.join("\n", registers)));
    }

    public void write(String filepath, String modifiedText) throws IOException {
        String textToWrite = (modifiedText != null) ? modifiedText : text;
        try (FileWriter fw = new FileWriter(filepath)) {
            fw.write(textToWrite);
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     PTXParser parser = new PTXParser();
    //     parser.open("example.ptx");
    //     parser.printProperties();
    //     parser.write("output.ptx", null);
    // }
}
  1. JavaScript class:
class PTXParser {
  constructor() {
    this.version = null;
    this.target = null;
    this.address_size = null;
    this.entries = [];
    this.funcs = [];
    this.variables = [];
    this.registers = [];
    this.text = null;
  }

  open(filepath, callback) {
    // Note: In Node.js; for browser, use FileReader as in the HTML example above
    const fs = require('fs');
    fs.readFile(filepath, 'utf8', (err, data) => {
      if (err) {
        console.error('Error opening file:', err);
        return;
      }
      this.text = data;
      this.decode();
      if (callback) callback();
    });
  }

  decode() {
    if (!this.text) {
      throw new Error('No file loaded');
    }
    const lines = this.text.split(/\r?\n/);
    for (let line of lines) {
      line = line.trim();
      if (!line || line.startsWith('//')) continue;
      if (line.startsWith('.version')) {
        this.version = line.split(/\s+/)[1];
      } else if (line.startsWith('.target')) {
        this.target = line.split(/\s+/)[1];
      } else if (line.startsWith('.address_size')) {
        this.address_size = line.split(/\s+/)[1];
      } else if (line.startsWith('.entry')) {
        const name = line.split(/\s+/)[1].split('(')[0];
        this.entries.push(name);
      } else if (line.startsWith('.func')) {
        const name = line.split(/\s+/)[1].split('(')[0];
        this.funcs.push(name);
      } else if (line.startsWith('.global') || line.startsWith('.const') || line.startsWith('.shared')) {
        this.variables.push(line);
      } else if (line.startsWith('.reg')) {
        this.registers.push(line);
      }
    }
  }

  print_properties() {
    console.log(`Version: ${this.version || 'Not found'}`);
    console.log(`Target: ${this.target || 'Not found'}`);
    console.log(`Address Size: ${this.address_size || '64 (default)'}`);
    console.log(`Entry Points: ${this.entries.length ? this.entries.join(', ') : 'None'}`);
    console.log(`Functions: ${this.funcs.length ? this.funcs.join(', ') : 'None'}`);
    console.log(`Variables:\n${this.variables.length ? this.variables.join('\n') : 'None'}`);
    console.log(`Registers:\n${this.registers.length ? this.registers.join('\n') : 'None'}`);
  }

  write(filepath, modified_text = null) {
    const fs = require('fs');
    const text_to_write = modified_text || this.text;
    fs.writeFile(filepath, text_to_write, (err) => {
      if (err) {
        console.error('Error writing file:', err);
      }
    });
  }
}

// Example usage (Node.js):
// const parser = new PTXParser();
// parser.open('example.ptx', () => {
//   parser.print_properties();
//   parser.write('output.ptx');
// });
  1. C++ class (using C++ for class support):
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <regex>

class PTXParser {
private:
    std::string version;
    std::string target;
    std::string address_size;
    std::vector<std::string> entries;
    std::vector<std::string> funcs;
    std::vector<std::string> variables;
    std::vector<std::string> registers;
    std::string text;

public:
    void open(const std::string& filepath) {
        std::ifstream file(filepath);
        if (!file.is_open()) {
            std::cerr << "Error opening file: " << filepath << std::endl;
            return;
        }
        std::stringstream buffer;
        buffer << file.rdbuf();
        text = buffer.str();
        file.close();
        decode();
    }

    void decode() {
        if (text.empty()) {
            throw std::runtime_error("No file loaded");
        }
        std::istringstream iss(text);
        std::string line;
        while (std::getline(iss, line)) {
            line.erase(0, line.find_first_not_of(" \t"));
            line.erase(line.find_last_not_of(" \t") + 1);
            if (line.empty() || line.substr(0, 2) == "//") continue;
            if (line.substr(0, 8) == ".version") {
                std::regex r("\\.version\\s+(\\S+)");
                std::smatch m;
                if (std::regex_search(line, m, r)) version = m[1];
            } else if (line.substr(0, 7) == ".target") {
                std::regex r("\\.target\\s+(\\S+)");
                std::smatch m;
                if (std::regex_search(line, m, r)) target = m[1];
            } else if (line.substr(0, 13) == ".address_size") {
                std::regex r("\\.address_size\\s+(\\S+)");
                std::smatch m;
                if (std::regex_search(line, m, r)) address_size = m[1];
            } else if (line.substr(0, 6) == ".entry") {
                std::regex r("\\.entry\\s+(\\S+)\\s*\\(?");
                std::smatch m;
                if (std::regex_search(line, m, r)) entries.push_back(m[1]);
            } else if (line.substr(0, 5) == ".func") {
                std::regex r("\\.func\\s+(\\S+)\\s*\\(?");
                std::smatch m;
                if (std::regex_search(line, m, r)) funcs.push_back(m[1]);
            } else if (line.substr(0, 7) == ".global" || line.substr(0, 6) == ".const" || line.substr(0, 7) == ".shared") {
                variables.push_back(line);
            } else if (line.substr(0, 4) == ".reg") {
                registers.push_back(line);
            }
        }
    }

    void print_properties() const {
        std::cout << "Version: " << (version.empty() ? "Not found" : version) << std::endl;
        std::cout << "Target: " << (target.empty() ? "Not found" : target) << std::endl;
        std::cout << "Address Size: " << (address_size.empty() ? "64 (default)" : address_size) << std::endl;
        std::cout << "Entry Points: ";
        if (entries.empty()) {
            std::cout << "None";
        } else {
            for (size_t i = 0; i < entries.size(); ++i) {
                std::cout << entries[i] << (i < entries.size() - 1 ? ", " : "");
            }
        }
        std::cout << std::endl;
        std::cout << "Functions: ";
        if (funcs.empty()) {
            std::cout << "None";
        } else {
            for (size_t i = 0; i < funcs.size(); ++i) {
                std::cout << funcs[i] << (i < funcs.size() - 1 ? ", " : "");
            }
        }
        std::cout << std::endl;
        std::cout << "Variables:" << std::endl;
        if (variables.empty()) {
            std::cout << "None" << std::endl;
        } else {
            for (const auto& var : variables) {
                std::cout << var << std::endl;
            }
        }
        std::cout << "Registers:" << std::endl;
        if (registers.empty()) {
            std::cout << "None" << std::endl;
        } else {
            for (const auto& reg : registers) {
                std::cout << reg << std::endl;
            }
        }
    }

    void write(const std::string& filepath, const std::string& modified_text = "") const {
        std::string text_to_write = modified_text.empty() ? text : modified_text;
        std::ofstream file(filepath);
        if (!file.is_open()) {
            std::cerr << "Error writing file: " << filepath << std::endl;
            return;
        }
        file << text_to_write;
        file.close();
    }
};

// Example usage:
// int main() {
//     PTXParser parser;
//     parser.open("example.ptx");
//     parser.print_properties();
//     parser.write("output.ptx");
//     return 0;
// }