summaryrefslogtreecommitdiff
path: root/utils/mcpu-as.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mcpu-as.c')
-rw-r--r--utils/mcpu-as.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/utils/mcpu-as.c b/utils/mcpu-as.c
new file mode 100644
index 0000000..a3b38c1
--- /dev/null
+++ b/utils/mcpu-as.c
@@ -0,0 +1,612 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libmcpu.h>
+#include <libmpuio.h>
+#include "mcpu-asm.h"
+#include "mcpu-object.h"
+#include "mcpu-version.h"
+
+#define CPU_AS_LINE_MAX 2147483647ULL
+
+typedef struct source_marker
+{
+ size_t physical_line;
+ uint64_t logical_line;
+ char *filename;
+} source_marker;
+
+typedef struct source_map
+{
+ source_marker *marker;
+ size_t count;
+ size_t capacity;
+} source_map;
+
+static char *
+readall( const char *name )
+{
+ FILE *f;
+ long z;
+ char *p;
+
+ f = fopen( name, "rb" );
+ if( !f )
+ return( NULL );
+ if( fseek( f, 0, SEEK_END ) != 0 )
+ {
+ fclose( f );
+ return( NULL );
+ }
+ z = ftell( f );
+ if( z < 0 )
+ {
+ fclose( f );
+ return( NULL );
+ }
+ rewind( f );
+ p = (char *)malloc( (size_t)z + 1U );
+ if( !p )
+ {
+ fclose( f );
+ return( NULL );
+ }
+ if( fread( p, 1, (size_t)z, f ) != (size_t)z )
+ {
+ free( p );
+ fclose( f );
+ return( NULL );
+ }
+ p[z] = 0;
+ fclose( f );
+ return( p );
+}
+
+static void
+usage( FILE *fp, const char *name )
+{
+ fprintf( fp,
+ "usage: %s [options] -o output.o input.s\n\n"
+ "Assemble UTF-8 LibMCPU source into a MELF ET_REL relocatable object.\n\n"
+ "Options:\n"
+ " -o <file> - write output to <file>\n"
+ " -v, --verbose - print output-object summary\n"
+ " -h, --help - show this help\n"
+ " --version - show program version\n"
+ " --dump-version - show version number only\n\n"
+ "Exactly one input source is required. The input file and -o option may\n"
+ "appear in either order.\n",
+ name);
+}
+
+static void
+source_map_free( source_map *map )
+{
+ size_t i;
+
+ if( !map )
+ return;
+ for( i = 0; i < map->count; ++i )
+ free( map->marker[i].filename );
+ free( map->marker );
+ memset( map, 0, sizeof( *map ) );
+}
+
+static int
+source_map_add( source_map *map, size_t physical_line, uint64_t logical_line,
+ const char *filename)
+{
+ source_marker *q;
+ size_t capacity;
+
+ if( map->count == map->capacity )
+ {
+ capacity = map->capacity ? map->capacity * 2U : 8U;
+ q = ( source_marker * ) realloc( map->marker, capacity *sizeof( *q ) );
+ if( !q )
+ return( -1 );
+ map->marker = q;
+ map->capacity = capacity;
+ }
+
+ map->marker[map->count].physical_line = physical_line;
+ map->marker[map->count].logical_line = logical_line;
+ map->marker[map->count].filename = strdup( filename ? filename : "<input>" );
+ if( !map->marker[map->count].filename )
+ return( -1 );
+ ++map->count;
+ return( 0 );
+}
+
+static const source_marker *
+source_map_lookup( const source_map *map, size_t physical_line )
+{
+ size_t i;
+ const source_marker *found = NULL;
+
+ for( i = 0; i < map->count; ++i )
+ {
+ if( map->marker[i].physical_line > physical_line )
+ break;
+ found = &map->marker[i];
+ }
+ return( found );
+}
+
+static uint64_t
+source_map_logical_line( const source_marker *m, size_t physical_line )
+{
+ uint64_t delta;
+
+ if( !m || physical_line < m->physical_line )
+ return (uint64_t)physical_line;
+ delta = (uint64_t)(physical_line - m->physical_line);
+ if( UINT64_MAX - m->logical_line < delta )
+ return( UINT64_MAX );
+ return( m->logical_line + delta );
+}
+
+static int
+horizontal_space( int c )
+{
+ return( c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r' );
+}
+
+/*
+ * Classify source-control lines left by a preprocessor.
+ *
+ * Return values:
+ * 0 ordinary source line
+ * 1 other '#' line: ignore its contents, but count the line normally
+ * 2 #pragma / #lang: ignore its contents and do not advance logical line
+ * 3 preprocessor line marker: # decimal ["filename"] [flags...]
+ * -1 malformed line marker
+ *
+ * A line marker applies to the physical source line immediately following it.
+ * Its decimal number is always base 10 and is restricted to 0..2147483647.
+ * If the filename is omitted, the previous logical filename is retained. Any
+ * text following the closing filename quote is deliberately ignored: those are
+ * preprocessor flags for later consumers and have no assembler semantics.
+ *
+ * The historical #line spelling is intentionally not recognized here. Like
+ * every other unrecognized '#' line, it is discarded without affecting
+ * assembly syntax and counts as one ordinary logical source line.
+ */
+static int
+parse_source_control( const char *line, uint64_t *next_line, char **new_filename,
+ char *why, size_t whysz )
+{
+ const char *p = line;
+ const char *digits;
+ const char *start;
+ uint64_t value = 0;
+ size_t n;
+ char *filename = NULL;
+
+ while( horizontal_space( (unsigned char)*p ) )
+ ++p;
+ if( *p != '#' )
+ return( 0 );
+ ++p;
+ while( horizontal_space( (unsigned char)*p ) )
+ ++p;
+
+ if( !strncmp( p, "pragma", 6 ) &&
+ (!p[6] || horizontal_space( (unsigned char)p[6] )) )
+ return( 2 );
+ if( !strncmp( p, "lang", 4 ) &&
+ (!p[4] || horizontal_space( (unsigned char)p[4] )) )
+ return( 2 );
+
+ if( !isdigit( (unsigned char)*p ) )
+ return( 1 );
+
+ digits = p;
+ while( isdigit( (unsigned char)*p ) )
+ ++p;
+
+ if( *p && !horizontal_space( (unsigned char)*p ) )
+ return( 1 );
+
+ while( horizontal_space( (unsigned char)*p ) )
+ ++p;
+ if( *p && *p != '"' )
+ return( 1 );
+
+ p = digits;
+ do
+ {
+ unsigned int digit = (unsigned int)(*p - '0');
+
+ if( value > ( CPU_AS_LINE_MAX - digit ) / 10ULL )
+ {
+ snprintf( why, whysz,
+ "preprocessor line marker value is outside 0..2147483647" );
+ return( -1 );
+ }
+ value = value * 10ULL + digit;
+ ++p;
+ }
+ while( isdigit( (unsigned char)*p ) );
+
+ while( horizontal_space( (unsigned char)*p ) )
+ ++p;
+ if( *p == '"' )
+ {
+ ++p;
+ start = p;
+ while( *p && *p != '"' )
+ ++p;
+ if( *p != '"' )
+ {
+ snprintf( why, whysz,
+ "unterminated filename in preprocessor line marker" );
+ return( -1 );
+ }
+ n = (size_t)(p - start);
+ filename = (char *)malloc( n + 1U );
+ if( !filename )
+ {
+ snprintf( why, whysz, "out of memory" );
+ return( -1 );
+ }
+ memcpy( filename, start, n );
+ filename[n] = 0;
+ }
+
+ *next_line = value;
+ *new_filename = filename;
+ return( 3 );
+}
+
+static void
+format_location_error( char *err, size_t errsz, const char *filename,
+ uint64_t line, const char *message)
+{
+ snprintf( err, errsz, "%s:%llu: error: %s",
+ filename ? filename : "<input>",
+ (unsigned long long)line,
+ message ? message : "invalid source");
+}
+
+/*
+ * cpu_asm's historical line scanner uses strtok_r(..., "\n", ...), which
+ * skips zero-length fields between adjacent newlines. That is harmless for
+ * assembly semantics, but it means its internal diagnostic line number does
+ * not advance across a physically empty source line.
+ *
+ * Preprocessor line markers, however, define logical source coordinates, and
+ * #pragma/#lang lines may be excluded from the logical counter. The physical
+ * line counter used by cpu_asm therefore has to stay synchronized with the
+ * source map maintained by mcpu-as, including across empty lines.
+ *
+ * Give each physically empty line a single horizontal-space character before
+ * passing the source to cpu_asm. trim() still treats the line as empty, so no
+ * assembly semantics change, while strtok_r now returns one token for every
+ * physical line and its diagnostic counter remains synchronized with the
+ * source map maintained by mcpu-as.
+ */
+static char *
+copy_preserving_empty_lines( const char *source )
+{
+ size_t len, extra = 0, i;
+ int at_line_start = 1;
+ char *copy, *q;
+
+ if( !source )
+ return( NULL );
+
+ len = strlen( source );
+ for( i = 0; i < len; ++i )
+ {
+ if( source[i] == '\n' )
+ {
+ if( at_line_start )
+ ++extra;
+ at_line_start = 1;
+ }
+ else
+ at_line_start = 0;
+ }
+
+ if( extra >= SIZE_MAX - len )
+ return( NULL );
+ copy = (char *)malloc( len + extra + 1U );
+ if( !copy )
+ return( NULL );
+
+ q = copy;
+ at_line_start = 1;
+ for( i = 0; i < len; ++i )
+ {
+ if( source[i] == '\n' )
+ {
+ if( at_line_start )
+ *q++ = ' ';
+ *q++ = '\n';
+ at_line_start = 1;
+ }
+ else
+ {
+ *q++ = source[i];
+ at_line_start = 0;
+ }
+ }
+ *q = 0;
+ return( copy );
+}
+
+/*
+ * Remove preprocessor-control lines from the token stream without changing
+ * physical line numbering and build a physical->logical source map for
+ * diagnostics.
+ */
+static int
+prepare_source( const char *source, const char *input_name, char **prepared,
+ source_map *map, char *err, size_t errsz)
+{
+ char *copy;
+ char *line;
+ size_t physical_line = 1;
+ const char *logical_file;
+
+ if( !source || !prepared || !map )
+ return( -1 );
+
+ memset( map, 0, sizeof( *map ) );
+ copy = copy_preserving_empty_lines( source );
+ if( !copy )
+ {
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ logical_file = input_name ? input_name : "<input>";
+ if( source_map_add( map, 1U, 1U, logical_file ) )
+ {
+ free( copy );
+ source_map_free( map );
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+
+ line = copy;
+ while( line && *line )
+ {
+ char *end = strchr( line, '\n' );
+ char saved = 0;
+ uint64_t requested_line = 0;
+ char *requested_file = NULL;
+ char why[160];
+ int parsed;
+ const source_marker *m;
+
+ if( end )
+ {
+ saved = *end;
+ *end = 0;
+ }
+
+ why[0] = 0;
+ parsed = parse_source_control( line, &requested_line, &requested_file,
+ why, sizeof( why ) );
+ if( parsed < 0 )
+ {
+ m = source_map_lookup( map, physical_line );
+ format_location_error( err, errsz,
+ m ? m->filename : logical_file,
+ source_map_logical_line( m, physical_line ), why);
+ free( requested_file );
+ if( end )
+ *end = saved;
+ free( copy );
+ source_map_free( map );
+ return( -1 );
+ }
+
+ if( parsed > 0 )
+ {
+ size_t len = strlen( line );
+
+ memset( line, ' ', len );
+ if( parsed == 3 )
+ {
+ const source_marker *current = source_map_lookup( map, physical_line );
+ const char *next_file = requested_file ? requested_file :
+ (current ? current->filename : logical_file);
+
+ if( source_map_add( map, physical_line + 1U, requested_line, next_file ) )
+ {
+ free( requested_file );
+ if( end )
+ *end = saved;
+ free( copy );
+ source_map_free( map );
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ }
+ else if( parsed == 2 )
+ {
+ const source_marker *current = source_map_lookup( map, physical_line );
+ uint64_t current_line = source_map_logical_line( current, physical_line );
+ const char *current_file = current ? current->filename : logical_file;
+
+ if( source_map_add( map, physical_line + 1U, current_line, current_file ) )
+ {
+ free( requested_file );
+ if( end )
+ *end = saved;
+ free( copy );
+ source_map_free( map );
+ snprintf( err, errsz, "out of memory" );
+ return( -1 );
+ }
+ }
+ }
+
+ free( requested_file );
+ if( end )
+ {
+ *end = saved;
+ line = end + 1;
+ ++physical_line;
+ }
+ else
+ break;
+ }
+
+ *prepared = copy;
+ return( 0 );
+}
+
+static void
+remap_assembler_error( char *err, size_t errsz, const source_map *map,
+ const char *input_name)
+{
+ char old[512];
+ const char *p;
+ char *end;
+ unsigned long long physical;
+ const source_marker *m;
+ uint64_t logical;
+
+ if( !err || !*err )
+ return;
+ if( strncmp( err, "line ", 5 ) != 0 )
+ return;
+
+ errno = 0;
+ p = err + 5;
+ physical = strtoull( p, &end, 10 );
+ if( errno || end == p || *end != ':' || physical == 0ULL ||
+ physical > (unsigned long long)SIZE_MAX)
+ return;
+
+ snprintf( old, sizeof( old ), "%s", end + 1 );
+ p = old;
+ while( *p == ' ' || *p == '\t' )
+ ++p;
+
+ m = source_map_lookup( map, (size_t)physical );
+ logical = source_map_logical_line( m, (size_t)physical );
+ format_location_error( err, errsz,
+ m ? m->filename : (input_name ? input_name : "<input>"),
+ logical, p);
+}
+
+int
+main( int ac, char **av )
+{
+ const char *input = NULL;
+ const char *output = NULL;
+ char *src = NULL;
+ char *prepared = NULL;
+ char err[512];
+ cpu_asm_object object;
+ source_map map;
+ int i;
+ int rc = 1;
+ int verbose = 0;
+
+ for( i = 1; i < ac; ++i )
+ {
+ mcpu_version_action version_action;
+
+ if( mcpu_version_parse_option(av[i], &version_action) )
+ return( mcpu_version_print(av[0], version_action) == 0 ? 0 : 1 );
+
+ if( !strcmp( av[i], "-h" ) || !strcmp( av[i], "--help" ) )
+ {
+ usage( stdout, av[0] );
+ return( 0 );
+ }
+ else if( !strcmp( av[i], "-o" ) )
+ {
+ if(++i >= ac || output )
+ {
+ usage( stderr, av[0] );
+ return( 2 );
+ }
+ output = av[i];
+ }
+ else if( !strcmp( av[i], "-v" ) || !strcmp( av[i], "--verbose" ) )
+ verbose = 1;
+ else if( av[i][0] == '-' )
+ {
+ fprintf( stderr, "mcpu-as: unknown option %s\n", av[i] );
+ return( 2 );
+ }
+ else
+ {
+ if( input )
+ {
+ fprintf( stderr, "mcpu-as: more than one input file\n" );
+ return( 2 );
+ }
+ input = av[i];
+ }
+ }
+
+ if( !input || !output )
+ {
+ usage( stderr, av[0] );
+ return( 2 );
+ }
+
+ __mpu_init();
+ src = readall( input );
+ if( !src )
+ {
+ perror( input );
+ goto out_mpu;
+ }
+ if( !mpu_utf8valid( (const __mpu_char8_t *)src ) )
+ {
+ fprintf( stderr, "mcpu-as: error: assembler source is not valid UTF-8\n" );
+ goto out_src;
+ }
+
+ err[0] = 0;
+ if( prepare_source( src, input, &prepared, &map, err, sizeof( err ) ) )
+ {
+ fprintf( stderr, "mcpu-as: %s\n", err[0] ? err : "cannot process source" );
+ goto out_src;
+ }
+
+ if( cpu_assemble_object( prepared, &object, err, sizeof( err ) ) )
+ {
+ remap_assembler_error( err, sizeof( err ), &map, input );
+ if( !strncmp( err, "line ", 5 ) || strstr( err, ": error: " ) )
+ fprintf( stderr, "mcpu-as: %s\n", err );
+ else
+ fprintf( stderr, "mcpu-as: error: %s\n", err );
+ goto out_map;
+ }
+
+ if( cpu_object_write( output, &object, err, sizeof( err ) ) )
+ {
+ fprintf( stderr, "mcpu-as: error: %s\n", err );
+ cpu_asm_object_free( &object );
+ goto out_map;
+ }
+
+ if( verbose )
+ printf( "MELF ET_REL: %zu symbols, %zu relocations -> %s\n",
+ object.symbol_count, object.reloc_count, output );
+ cpu_asm_object_free( &object );
+ rc = 0;
+
+out_map:
+ source_map_free( &map );
+ free( prepared );
+out_src:
+ free( src );
+out_mpu:
+ __mpu_free_context();
+ return( rc );
+}