summaryrefslogtreecommitdiff
path: root/utils/mcpu-asm.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/mcpu-asm.c')
-rw-r--r--utils/mcpu-asm.c4012
1 files changed, 4012 insertions, 0 deletions
diff --git a/utils/mcpu-asm.c b/utils/mcpu-asm.c
new file mode 100644
index 0000000..dcc4ead
--- /dev/null
+++ b/utils/mcpu-asm.c
@@ -0,0 +1,4012 @@
+#include <ctype.h>
+#include <errno.h>
+#include <math.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <libmcpu.h>
+#include <libmpuio.h>
+#include "mcpu-asm.h"
+
+#define ASM_NAME_MAX 64
+#define ASM_EXPR_MAX 256
+
+typedef struct
+{
+ char name[ASM_NAME_MAX];
+ __mpu_uint64_t addr;
+} label_t;
+typedef struct
+{
+ size_t pos;
+ char expr[ASM_EXPR_MAX];
+} fixup_t;
+typedef struct
+{
+ char name[ASM_NAME_MAX];
+ char expr[ASM_EXPR_MAX];
+ int line;
+ int state; /* 0=not evaluated, 1=evaluating, 2=done */
+ __int128 value;
+} equ_t;
+typedef struct
+{
+ __mpu_UNIT_t *p;
+ size_t n, cap;
+} buf_t;
+typedef struct
+{
+ label_t *p;
+ size_t n, cap;
+} labels_t;
+typedef struct
+{
+ fixup_t *p;
+ size_t n, cap;
+} fixups_t;
+typedef struct
+{
+ equ_t *p;
+ size_t n, cap;
+} equs_t;
+
+typedef struct
+{
+ char name[ASM_NAME_MAX];
+ int is_global;
+ int is_extern;
+ int is_weak;
+} symdecl_t;
+typedef struct
+{
+ symdecl_t *p;
+ size_t n, cap;
+} symdecls_t;
+
+static int
+grow( void **p, size_t *cap, size_t need, size_t unit )
+{
+ size_t c = *cap ? *cap : 64;
+ void *q;
+ while( c < need ) c *= 2;
+ q = realloc( *p, c *unit );
+ if( !q ) return( -1 );
+ *p = q;
+ *cap = c;
+ return( 0 );
+}
+
+static int
+put( buf_t *b, const void *p, size_t n )
+{
+ if( grow( (void **)&b->p, &b->cap, b->n + n, 1 ) ) return( -1 );
+ memcpy( b->p + b->n, p, n );
+ b->n += n;
+ return( 0 );
+}
+
+static int
+put8( buf_t *b, unsigned v )
+{
+ __mpu_UNIT_t x = (__mpu_UNIT_t)v;
+ return( put( b, &x, 1 ) );
+}
+
+static int
+put16( buf_t *b, unsigned v )
+{
+ __mpu_UNIT_t x[2];
+ __cpu_code_put_u16( x, (__mpu_uint16_t)v );
+ return( put( b, x, 2 ) );
+}
+
+static int
+put64( buf_t *b, __mpu_uint64_t v )
+{
+ __mpu_UNIT_t x[8];
+ __cpu_code_put_u64( x, v );
+ return( put( b, x, 8 ) );
+}
+
+static char *
+trim( char *s )
+{
+ char *e;
+ while( isspace( (unsigned char)*s ) ) s++;
+ e = s + strlen( s );
+ while( e > s && isspace( (unsigned char)e[-1] ) ) *--e = 0;
+ return( s );
+}
+
+static int
+streqi( const char *a, const char *b )
+{
+ for( ; *a && *b; a++, b++ ) if( toupper( (unsigned char)*a ) != toupper( (unsigned char)*b ) ) return( 0 );
+ return( !*a && !*b );
+}
+
+/* ';' starts a comment only outside a quoted assembler string. */
+static void
+strip_comment( char *s )
+{
+ int quote = 0, esc = 0;
+ for( ; *s; s++ )
+ {
+ if( quote )
+ {
+ if( esc )
+ {
+ esc = 0;
+ continue;
+ }
+ if( *s == '\\' )
+ {
+ esc = 1;
+ continue;
+ }
+ if( *s == '"' )
+ {
+ quote = 0;
+ continue;
+ }
+ }
+ else
+ {
+ if( *s == '"' )
+ {
+ quote = 1;
+ continue;
+ }
+ if( *s == ';' )
+ {
+ *s = 0;
+ return;
+ }
+ }
+ }
+}
+
+/* A label colon is recognized only in the first token, never inside operands. */
+static char *
+label_colon( char *s )
+{
+ char *p = s;
+ while( *p && !isspace( (unsigned char)*p ) )
+ {
+ if( *p == ':' ) return( p );
+ p++;
+ }
+ return( 0 );
+}
+
+static int
+addlabel( labels_t *l, const char *n, __mpu_uint64_t a )
+{
+ size_t i;
+ for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) ) return( -1 );
+ if( strlen( n ) >= ASM_NAME_MAX ) return( -1 );
+ if( grow( (void **)&l->p, &l->cap, l->n + 1, sizeof( *l->p ) ) ) return( -1 );
+ strcpy( l->p[l->n].name, n );
+ l->p[l->n++].addr = a;
+ return( 0 );
+}
+
+static int
+findlabel( const labels_t *l, const char *n, __mpu_uint64_t *a )
+{
+ size_t i;
+ for( i = 0; i < l->n; i++ ) if( !strcmp( l->p[i].name, n ) )
+ {
+ *a = l->p[i].addr;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+addfix( fixups_t *f, size_t pos, const char *expr )
+{
+ if( strlen( expr ) >= ASM_EXPR_MAX ) return( -1 );
+ if( grow( (void **)&f->p, &f->cap, f->n + 1, sizeof( *f->p ) ) ) return( -1 );
+ f->p[f->n].pos = pos;
+ strcpy( f->p[f->n].expr, expr );
+ f->n++;
+ return( 0 );
+}
+
+static int
+findequ( const equs_t *e, const char *n, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < e->n; i++ ) if( !strcmp( e->p[i].name, n ) )
+ {
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+addequ( equs_t *e, const char *n, const char *expr, int line, char *err, size_t es )
+{
+ size_t dummy;
+ if( !*n || strlen( n ) >= ASM_NAME_MAX || strlen( expr ) >= ASM_EXPR_MAX )
+ {
+ snprintf( err, es, "bad .equ name/expression" );
+ return( -1 );
+ }
+ if( !findequ( e, n, &dummy ) )
+ {
+ snprintf( err, es, "duplicate .equ symbol: %s", n );
+ return( -1 );
+ }
+ if( grow( (void **)&e->p, &e->cap, e->n + 1, sizeof( *e->p ) ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ memset( &e->p[e->n], 0, sizeof( e->p[e->n] ) );
+ strcpy( e->p[e->n].name, n );
+ strcpy( e->p[e->n].expr, expr );
+ e->p[e->n].line = line;
+ e->n++;
+ return( 0 );
+}
+
+/* Split comma-separated arguments while respecting parentheses and quoted strings. */
+static int
+splitargs( char *s, char **a, int max )
+{
+ int n = 0, depth = 0, quote = 0, esc = 0;
+ char *p = s, *start = s;
+ for( ; ; p++ )
+ {
+ int end = ( *p == 0 );
+ if( !end )
+ {
+ if( quote )
+ {
+ if( esc ) esc = 0;
+ else if( *p == '\\' ) esc = 1;
+ else if( *p == '"' ) quote = 0;
+ }
+ else
+ {
+ if( *p == '"' ) quote = 1;
+ else if( *p == '(' ) depth++;
+ else if( *p == ')' ) depth--;
+ }
+ }
+ if( ( !quote && depth == 0 && *p == ',' ) || end )
+ {
+ if( n >= max ) return( -1 );
+ if( !end ) * p = 0;
+ a[n++] = trim( start );
+ if( end ) break;
+ start = p + 1;
+ }
+ }
+ if( quote || depth != 0 ) return( -1 );
+ return( n );
+}
+
+/* ------------------------- integer expressions ------------------------- */
+typedef struct
+{
+ const char *p;
+ equs_t *eqs;
+ const labels_t *labels; /* optional section-local label namespace */
+ char *err;
+ size_t es;
+} expr_parser_t;
+
+static int eval_expression( const char *, equs_t *, const labels_t *, __int128 *, char *, size_t );
+
+static void
+expr_skip( expr_parser_t *x )
+{
+ while( isspace( (unsigned char)*x->p ) ) x->p++;
+}
+
+static int
+symfirst( int c )
+{
+ return( isalpha( (unsigned char)c ) || c == '_' || c == '.' || c == '$' );
+}
+
+static int
+symrest( int c )
+{
+ return( isalnum( (unsigned char)c ) || c == '_' || c == '.' || c == '$' );
+}
+
+static int
+equ_value( equs_t *eqs, size_t i, __int128 *v, char *err, size_t es )
+{
+ equ_t *e = &eqs->p[i];
+ if( e->state == 2 )
+ {
+ *v = e->value;
+ return( 0 );
+ }
+ if( e->state == 1 )
+ {
+ snprintf( err, es, "cyclic .equ definition involving %s", e->name );
+ return( -1 );
+ }
+ e->state = 1;
+ if( eval_expression( e->expr, eqs, 0, &e->value, err, es ) )
+ {
+ e->state = 0;
+ return( -1 );
+ }
+ e->state = 2;
+ *v = e->value;
+ return( 0 );
+}
+
+static int
+expr_symbol( expr_parser_t *x, const char *name, __int128 *v )
+{
+ size_t i;
+ __mpu_uint64_t a;
+ if( !findequ( x->eqs, name, &i ) ) return( equ_value( x->eqs, i, v, x->err, x->es ) );
+ if( x->labels && !findlabel( x->labels, name, &a ) )
+ {
+ *v = ( __int128 ) a;
+ return( 0 );
+ }
+ snprintf( x->err, x->es, "unknown symbol in expression: %s", name );
+ return( -1 );
+}
+
+static int
+expr_primary( expr_parser_t *x, __int128 *v )
+{
+ char name[ASM_NAME_MAX], *end;
+ size_t n = 0;
+ unsigned long long u;
+ expr_skip( x );
+ if( symfirst( (unsigned char)*x->p ) )
+ {
+ while( symrest( (unsigned char)*x->p ) )
+ {
+ if( n + 1 >= sizeof( name ) )
+ {
+ snprintf( x->err, x->es, "symbol name too long" );
+ return( -1 );
+ }
+ name[n++] = *x->p++;
+ }
+ name[n] = 0;
+ return( expr_symbol( x, name, v ) );
+ }
+ errno = 0;
+ u = strtoull( x->p, &end, 0 );
+ if( end == x->p )
+ {
+ snprintf( x->err, x->es, "expected integer or symbol in expression" );
+ return( -1 );
+ }
+ if( errno == ERANGE )
+ {
+ snprintf( x->err, x->es, "integer literal out of 64-bit range in expression" );
+ return( -1 );
+ }
+ x->p = end;
+ *v = ( __int128 ) u;
+ return( 0 );
+}
+
+/* Forward declarations for recursive-descent parentheses. */
+static int expr_add( expr_parser_t *, __int128 * );
+
+static int
+expr_atom( expr_parser_t *x, __int128 *v )
+{
+ expr_skip( x );
+ if( *x->p == '(' )
+ {
+ x->p++;
+ if( expr_add( x, v ) ) return( -1 );
+ expr_skip( x );
+ if( *x->p != ')' )
+ {
+ snprintf( x->err, x->es, "missing ')' in expression" );
+ return( -1 );
+ }
+ x->p++;
+ return( 0 );
+ }
+ return( expr_primary( x, v ) );
+}
+
+static int
+expr_unary( expr_parser_t *x, __int128 *v )
+{
+ expr_skip( x );
+ if( *x->p == '+' )
+ {
+ x->p++;
+ return( expr_unary( x, v ) );
+ }
+ if( *x->p == '-' )
+ {
+ __int128 t;
+ x->p++;
+ if( expr_unary( x, &t ) ) return( -1 );
+ if( __builtin_sub_overflow( ( __int128 ) 0, t, v ) )
+ {
+ snprintf( x->err, x->es, "expression overflow" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+ return( expr_atom( x, v ) );
+}
+
+static int
+expr_mul( expr_parser_t *x, __int128 *v )
+{
+ __int128 a, b, r;
+ char op;
+ if( expr_unary( x, &a ) ) return( -1 );
+ for( ; ; )
+ {
+ expr_skip( x );
+ op = *x->p;
+ if( op != '*' && op != '/' && op != '%' ) break;
+ x->p++;
+ if( expr_unary( x, &b ) ) return( -1 );
+ if( op == '*' )
+ {
+ if( __builtin_mul_overflow( a, b, &r ) )
+ {
+ snprintf( x->err, x->es, "expression overflow" );
+ return( -1 );
+ }
+ }
+ else
+ {
+ if( !b )
+ {
+ snprintf( x->err, x->es, "division by zero in expression" );
+ return( -1 );
+ }
+ if( op == '/' ) r = a / b;
+ else r = a % b;
+ }
+ a = r;
+ }
+ *v = a;
+ return( 0 );
+}
+
+static int
+expr_add( expr_parser_t *x, __int128 *v )
+{
+ __int128 a, b, r;
+ char op;
+ if( expr_mul( x, &a ) ) return( -1 );
+ for( ; ; )
+ {
+ expr_skip( x );
+ op = *x->p;
+ if( op != '+' && op != '-' ) break;
+ x->p++;
+ if( expr_mul( x, &b ) ) return( -1 );
+ if( op == '+' )
+ {
+ if( __builtin_add_overflow( a, b, &r ) )
+ {
+ snprintf( x->err, x->es, "expression overflow" );
+ return( -1 );
+ }
+ }
+ else
+ {
+ if( __builtin_sub_overflow( a, b, &r ) )
+ {
+ snprintf( x->err, x->es, "expression overflow" );
+ return( -1 );
+ }
+ }
+ a = r;
+ }
+ *v = a;
+ return( 0 );
+}
+
+static int
+eval_expression( const char *s, equs_t *eqs, const labels_t *labels, __int128 *v, char *err, size_t es )
+{
+ expr_parser_t x =
+ {
+ s, eqs, labels, err, es
+ };
+ if( expr_add( &x, v ) ) return( -1 );
+ expr_skip( &x );
+ if( *x.p )
+ {
+ snprintf( err, es, "unexpected token in expression near: %s", x.p );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+static int
+eval_i64( const char *s, equs_t *eqs, const labels_t *l, __mpu_int64_t *v, char *err, size_t es )
+{
+ __int128 x;
+ if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 );
+ if( x < ( __int128 ) INT64_MIN || x > ( __int128 ) INT64_MAX ) return( 1 );
+ *v = (__mpu_int64_t)x;
+ return( 0 );
+}
+
+static int
+eval_u64( const char *s, equs_t *eqs, const labels_t *l, __mpu_uint64_t *v, char *err, size_t es )
+{
+ __int128 x;
+ if( eval_expression( s, eqs, l, &x, err, es ) ) return( -1 );
+ if( x < 0 || x > ( __int128 ) UINT64_MAX ) return( 1 );
+ *v = (__mpu_uint64_t)x;
+ return( 0 );
+}
+
+static int
+plain_integer_literal( const char *s )
+{
+ const unsigned char *p = (const unsigned char *)s;
+ int base = 10, nd = 0;
+ while( isspace( *p ) ) p++;
+ if( *p == '+' || *p == '-' ) p++;
+ if( !*p ) return( 0 );
+ if( p[0] == '0' && ( p[1] == 'x' || p[1] == 'X' ) )
+ {
+ base = 16;
+ p += 2;
+ }
+ else
+ if( p[0] == '0' && ( p[1] == 'b' || p[1] == 'B' ) )
+ {
+ base = 2;
+ p += 2;
+ }
+ else
+ if( p[0] == '0' && isdigit( p[1] ) )
+ {
+ base = 8;
+ p++;
+ }
+ while( *p && !isspace( *p ) )
+ {
+ int ok = ( base == 16 ) ? isxdigit( *p ) : ( base == 10 ) ? isdigit( *p ) : ( base == 8 ) ? ( *p >= '0' && *p <= '7' ) : ( *p == '0' || *p == '1' );
+ if( !ok ) return( 0 );
+ p++;
+ nd++;
+ }
+ while( isspace( *p ) ) p++;
+ return( nd && !*p );
+}
+
+static void
+i128_decimal( __int128 v, char out[64] )
+{
+ char tmp[64];
+ int neg = v < 0, n = 0, i = 0;
+ unsigned __int128 u;
+ if( neg ) u = (unsigned __int128)( - ( v + 1 ) ) + 1;
+ else u = (unsigned __int128)v;
+ do
+ {
+ tmp[n++] = (char)( '0' + u % 10 );
+ u /= 10;
+ }
+ while( u );
+ if( neg ) out[i++] = '-';
+ while( n ) out[i++] = tmp[--n];
+ out[i] = 0;
+}
+
+/* --------------------------- register syntax --------------------------- */
+static int
+regparse( const char *s, cpu_register *r, int *isc, equs_t *eqs, char *err, size_t es )
+{
+ const char *p = s, *at;
+ char *e;
+ unsigned long bits;
+ __mpu_uint64_t a;
+ while( isspace( (unsigned char)*p ) ) p++;
+ if( *p != 'r' && *p != 'R' && *p != 'c' && *p != 'C' ) return( -1 );
+ *isc = ( *p == 'c' || *p == 'C' );
+ p++;
+ errno = 0;
+ bits = strtoul( p, &e, 10 );
+ if( errno || e == p || *e != '@' ) return( -1 );
+ at = e + 1;
+ if( !*at ) return( -1 );
+ if( eval_u64( at, eqs, 0, &a, err, es ) != 0 || a > UINT32_MAX ) return( -1 );
+ r->bits = (unsigned int)bits;
+ r->address = (__mpu_uint32_t)a;
+ if( !__cpu_reg_valid( r->bits, r->address ) ) return( -1 );
+ if( *isc && r->bits < 64 ) return( -1 );
+ return( 0 );
+}
+
+static int
+preg( buf_t *b, const char *s, int require_c, int require_r, equs_t *eqs, char *err, size_t es )
+{
+ cpu_register r;
+ int c;
+ __mpu_UNIT_t x[3];
+ if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) )
+ {
+ snprintf( err, es, "bad register: %s", s );
+ return( -1 );
+ }
+ if( require_c && !c )
+ {
+ snprintf( err, es, "complex instruction requires c<bits>@addr: %s", s );
+ return( -1 );
+ }
+ if( require_r && c )
+ {
+ snprintf( err, es, "instruction requires r<bits>@addr: %s", s );
+ return( -1 );
+ }
+ if( __cpu_encode_reg24( x, &r ) ) return( -1 );
+ return( put( b, x, 3 ) );
+}
+
+static int
+preg_r64( buf_t *b, const char *s, const char *what, equs_t *eqs, char *err, size_t es )
+{
+ cpu_register r;
+ int c;
+ __mpu_UNIT_t x[3];
+ if( regparse( trim( (char *)s ), &r, &c, eqs, err, es ) || c || r.bits != 64 )
+ {
+ snprintf( err, es, "%s requires r64@addr: %s", what, s );
+ return( -1 );
+ }
+ if( __cpu_encode_reg24( x, &r ) ) return( -1 );
+ return( put( b, x, 3 ) );
+}
+
+static int
+preg_ptr( buf_t *b, const char *s, equs_t *eqs, char *err, size_t es )
+{
+ return( preg_r64( b, s, "PTR64 operand", eqs, err, es ) );
+}
+
+/* ---------------------------- string literals ---------------------------- */
+static int
+hex_digit( int c )
+{
+ if( c >= '0' && c <= '9' ) return( c - '0' );
+ if( c >= 'a' && c <= 'f' ) return( c - 'a' + 10 );
+ if( c >= 'A' && c <= 'F' ) return( c - 'A' + 10 );
+ return( -1 );
+}
+
+static int
+put_utf8_cp( buf_t *b, unsigned long cp, char *err, size_t es )
+{
+ unsigned char x[4];
+ size_t n;
+ if( cp == 0 )
+ {
+ snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" );
+ return( -1 );
+ }
+ if( cp <= 0x7f )
+ {
+ x[0] = (unsigned char)cp;
+ n = 1;
+ }
+ else
+ if( cp <= 0x7ff )
+ {
+ x[0] = 0xc0 | ( cp >> 6 );
+ x[1] = 0x80 | ( cp & 0x3f );
+ n = 2;
+ }
+ else
+ if( cp <= 0xffff )
+ {
+ if( cp >= 0xd800 && cp <= 0xdfff )
+ {
+ snprintf( err, es, "Unicode surrogate is not a valid scalar value" );
+ return( -1 );
+ }
+ x[0] = 0xe0 | ( cp >> 12 );
+ x[1] = 0x80 | ( ( cp >> 6 ) & 0x3f );
+ x[2] = 0x80 | ( cp & 0x3f );
+ n = 3;
+ }
+ else
+ if( cp <= 0x10ffff )
+ {
+ x[0] = 0xf0 | ( cp >> 18 );
+ x[1] = 0x80 | ( ( cp >> 12 ) & 0x3f );
+ x[2] = 0x80 | ( ( cp >> 6 ) & 0x3f );
+ x[3] = 0x80 | ( cp & 0x3f );
+ n = 4;
+ }
+ else
+ {
+ snprintf( err, es, "Unicode code point out of range" );
+ return( -1 );
+ }
+ if( put( b, x, n ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+static int
+parse_utf8_string( const char *s, buf_t *out, char *err, size_t es )
+{
+ const unsigned char *p = (const unsigned char *)s;
+ int closed = 0;
+ memset( out, 0, sizeof( *out ) );
+ while( isspace( *p ) ) p++;
+ if( *p != '"' )
+ {
+ snprintf( err, es, "expected quoted string" );
+ return( -1 );
+ }
+ p++;
+ while( *p )
+ {
+ if( *p == '"' )
+ {
+ p++;
+ closed = 1;
+ break;
+ }
+ if( *p != '\\' )
+ {
+ if( *p == 0 )
+ {
+ break;
+ }
+ if( put8( out, *p++ ) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto fail;
+ } continue;
+ }
+ p++;
+ if( !*p )
+ {
+ snprintf( err, es, "unfinished escape sequence" );
+ goto fail;
+ }
+ switch( *p++ )
+ {
+ case '\\': if( put8( out, '\\' ) ) goto oom;
+ break;
+ case '"': if( put8( out, '"' ) ) goto oom;
+ break;
+ case 'n': if( put8( out, '\n' ) ) goto oom;
+ break;
+ case 'r': if( put8( out, '\r' ) ) goto oom;
+ break;
+ case 't': if( put8( out, '\t' ) ) goto oom;
+ break;
+ case 'b': if( put8( out, '\b' ) ) goto oom;
+ break;
+ case 'f': if( put8( out, '\f' ) ) goto oom;
+ break;
+ case 'v': if( put8( out, '\v' ) ) goto oom;
+ break;
+ case 'a': if( put8( out, '\a' ) ) goto oom;
+ break;
+ case '0': snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" );
+ goto fail;
+ case 'x': {
+ int h1, h2;
+ unsigned v;
+ if( !p[0] || !p[1] )
+ {
+ snprintf( err, es, "\\x escape requires exactly two hex digits" );
+ goto fail;
+ }
+ h1 = hex_digit( p[0] );
+ h2 = hex_digit( p[1] );
+ if( h1 < 0 || h2 < 0 )
+ {
+ snprintf( err, es, "\\x escape requires exactly two hex digits" );
+ goto fail;
+ }
+ v = (unsigned)( ( h1 << 4 ) | h2 );
+ p += 2;
+ if( v == 0 )
+ {
+ snprintf( err, es, "embedded NUL is not supported in .utf8/.ucs2; use .byte/.integer" );
+ goto fail;
+ }
+ if( put8( out, v ) ) goto oom;
+ break;
+ }
+ case 'u':
+ case 'U': {
+ int nd = p[-1] == 'u' ? 4 : 8, i, h;
+ unsigned long cp = 0;
+ for( i = 0; i < nd; i++ )
+ {
+ if( !p[i] )
+ {
+ snprintf( err, es, "Unicode escape requires %d hex digits", nd );
+ goto fail;
+ }
+ h = hex_digit( p[i] );
+ if( h < 0 )
+ {
+ snprintf( err, es, "Unicode escape requires %d hex digits", nd );
+ goto fail;
+ }
+ cp = ( cp << 4 ) | (unsigned)h;
+ }
+ p += nd;
+ if( put_utf8_cp( out, cp, err, es ) ) goto fail;
+ break;
+ }
+ default: snprintf( err, es, "unknown string escape sequence" );
+ goto fail;
+ }
+
+ }
+ if( !closed )
+ {
+ snprintf( err, es, "unterminated string literal" );
+ goto fail;
+ }
+
+ while( isspace( *p ) ) p++;
+ if( *p )
+ {
+ snprintf( err, es, "unexpected text after string literal" );
+ goto fail;
+ }
+
+ if( put8( out, 0 ) ) goto oom;
+ if( !mpu_utf8valid( (const __mpu_char8_t *)out->p ) )
+ {
+ snprintf( err, es, "string literal is not valid UTF-8" );
+ goto fail;
+ }
+
+ return( 0 );
+oom:
+ snprintf( err, es, "out of memory" );
+fail:
+ free( out->p );
+ memset( out, 0, sizeof( *out ) );
+ return( -1 );
+}
+
+static int
+real_literal_exact_value( const char *s, int nb, mpu_real *out )
+{
+ mpu_wide_real_t src, narrow, back;
+ memset( src, 0, sizeof( src ) );
+ memset( narrow, 0, sizeof( narrow ) );
+ memset( back, 0, sizeof( back ) );
+ if( ascii_to_real( ( mpu_real * ) src, (__mpu_char8_t *)s, NB_R65536 ) < 0 ) return( -1 );
+ r_cvt( ( mpu_real * ) narrow, ( mpu_real * ) src, nb, NB_R65536 );
+ r_cvt( ( mpu_real * ) back, ( mpu_real * ) narrow, NB_R65536, nb );
+ if( r_cmp( ( mpu_real * ) src, ( mpu_real * ) back, NB_R65536 ) != 0 ) return( 0 );
+ if( out ) memcpy( out, narrow, (size_t)nb );
+ return( 1 );
+}
+
+static int
+cimm_exact( const char *s, float *r, float *j, char *err, size_t es )
+{
+ const char *rp = strstr( s, "r0" ), *jp;
+ char *rs = 0, *js = 0;
+ size_t rn, jn;
+ int ex;
+ if( !rp || rp == s ) return( -1 );
+ jp = strstr( rp + 2, "j0" );
+ if( !jp || jp == rp + 2 || jp[2] ) return( -1 );
+ rn = (size_t)( rp - s );
+ jn = (size_t)( jp - ( rp + 2 ) );
+ rs = (char *)malloc( rn + 1 );
+ js = (char *)malloc( jn + 1 );
+ if( !rs || !js )
+ {
+ free( rs );
+ free( js );
+ snprintf( err, es, "out of memory" );
+ return( -2 );
+ }
+ memcpy( rs, s, rn );
+ rs[rn] = 0;
+ memcpy( js, rp + 2, jn );
+ js[jn] = 0;
+ {
+ mpu_real32_t xr, xj;
+ ex = real_literal_exact_value( rs, NB_R32, ( mpu_real * ) xr );
+ if( ex != 1 )
+ {
+ free( rs );
+ free( js );
+ if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" );
+ return( ex == 0 ? -2 : -1 );
+ }
+ ex = real_literal_exact_value( js, NB_R32, ( mpu_real * ) xj );
+ if( ex != 1 )
+ {
+ free( rs );
+ free( js );
+ if( ex == 0 ) snprintf( err, es, "LDC immediate component is not exactly representable as real32;\nuse .complex <bits>, real, imag in DATA and LOAD" );
+ return( ex == 0 ? -2 : -1 );
+ }
+ memcpy( r, xr, NB_R32 );
+ memcpy( j, xj, NB_R32 );
+ }
+ free( rs );
+ free( js );
+ return( 0 );
+}
+
+static int
+constid( const char *s, unsigned *v )
+{
+ struct K
+ {
+ const char *n;
+ unsigned v;
+ }
+ ks[] =
+ {
+ { "ZERO", CPU_RCONST_ZERO },
+ { "HALF", CPU_RCONST_HALF },
+ { "ONE", CPU_RCONST_ONE },
+ { "TWO", CPU_RCONST_TWO },
+ { "TEN", CPU_RCONST_TEN },
+ { "MTEN", CPU_RCONST_MTEN },
+ { "32", CPU_RCONST_32 },
+ { "PI", CPU_RCONST_PI },
+ { "E", CPU_RCONST_E },
+ { "1_LN2", CPU_RCONST_1_LN2 },
+ { "LN2", CPU_RCONST_LN2 },
+ { "1_LN10", CPU_RCONST_1_LN10 },
+ { "LN10", CPU_RCONST_LN10 },
+ { "1_LG2", CPU_RCONST_1_LG2 },
+ { "LG2", CPU_RCONST_LG2 },
+ { "PI_2", CPU_RCONST_PI_2 },
+ { "PI_3", CPU_RCONST_PI_3 },
+ { "PI_4", CPU_RCONST_PI_4 },
+ { "PI_5", CPU_RCONST_PI_5 },
+ { "PI_6", CPU_RCONST_PI_6 },
+ { "1_PI", CPU_RCONST_1_PI },
+ { "2_PI", CPU_RCONST_2_PI },
+ { "3_PI", CPU_RCONST_3_PI },
+ { "4_PI", CPU_RCONST_4_PI },
+ { "5_PI", CPU_RCONST_5_PI },
+ { "2PI", CPU_RCONST_2PI },
+ { "3PI", CPU_RCONST_3PI },
+ { "1_2PI", CPU_RCONST_1_2PI },
+ { "1_3PI", CPU_RCONST_1_3PI },
+ { "1_4PI", CPU_RCONST_1_4PI },
+ { "1_5PI", CPU_RCONST_1_5PI },
+ { "1_6PI", CPU_RCONST_1_6PI },
+ { "3PI_4", CPU_RCONST_3PI_4 },
+ { "SQRTPI", CPU_RCONST_SQRTPI },
+ { "1_SQRTPI", CPU_RCONST_1_SQRTPI },
+ { "2_SQRTPI", CPU_RCONST_2_SQRTPI },
+ { "SQRT2", CPU_RCONST_SQRT2 },
+ { "1_SQRT2", CPU_RCONST_1_SQRT2 },
+ { "SQRT3", CPU_RCONST_SQRT3 },
+ { "1_SQRT3", CPU_RCONST_1_SQRT3 },
+ { "DEGREE", CPU_RCONST_DEGREE },
+ { "1_DEGREE", CPU_RCONST_1_DEGREE },
+ { "GOLDENRATIO", CPU_RCONST_GOLDENRATIO },
+ { "EULERGAMMA", CPU_RCONST_EULERGAMMA },
+ { "CATALAN", CPU_RCONST_CATALAN },
+ { "EPSILON", CPU_RCONST_EPSILON },
+ { "IND", CPU_RCONST_IND },
+ { "PNAN", CPU_RCONST_PNAN },
+ { "NNAN", CPU_RCONST_NNAN },
+ { "PNANMAX", CPU_RCONST_PNANMAX },
+ { "NNANMAX", CPU_RCONST_NNANMAX },
+ { "PNANMIN", CPU_RCONST_PNANMIN },
+ { "NNANMIN", CPU_RCONST_NNANMIN },
+ { "PINF", CPU_RCONST_PINF },
+ { "NINF", CPU_RCONST_NINF },
+ { "PREALMIN", CPU_RCONST_PREALMIN },
+ { "NREALMIN", CPU_RCONST_NREALMIN },
+ { "PREALMAX", CPU_RCONST_PREALMAX },
+ { "NREALMAX", CPU_RCONST_NREALMAX },
+ { "PZERO", CPU_RCONST_PZERO },
+ { "NZERO", CPU_RCONST_NZERO }
+ };
+ size_t i;
+ for( i = 0; i < sizeof( ks ) / sizeof( ks[0] ); i++ ) if( streqi( s, ks[i].n ) )
+ {
+ *v = ks[i].v;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int
+pswbitid( const char *s, unsigned *v )
+{
+ struct B
+ {
+ const char *n;
+ unsigned v;
+ }
+ bs[] =
+ {
+ { "AF", CPU_PSWBIT_AF },
+ { "CF", CPU_PSWBIT_CF },
+ { "OF", CPU_PSWBIT_OF },
+ { "SF", CPU_PSWBIT_SF },
+ { "PF", CPU_PSWBIT_PF },
+ { "ZF", CPU_PSWBIT_ZF },
+ { "RF", CPU_PSWBIT_RF },
+ { "VF", CPU_PSWBIT_VF },
+ { "DOMF", CPU_PSWBIT_DOMF },
+ { "SNGF", CPU_PSWBIT_SNGF },
+ { "OVFF", CPU_PSWBIT_OVFF },
+ { "UDFF", CPU_PSWBIT_UDFF },
+ { "TLSF", CPU_PSWBIT_TLSF },
+ { "PLSF", CPU_PSWBIT_PLSF },
+ { "INDF", CPU_PSWBIT_INDF },
+ { "INXF", CPU_PSWBIT_INXF },
+ { "HF", CPU_PSWBIT_HF },
+ { "TF", CPU_PSWBIT_TF },
+ { "IF", CPU_PSWBIT_IF }
+ };
+ size_t i;
+ for( i = 0; i < sizeof( bs ) / sizeof( bs[0] ); i++ )
+ if( streqi( s, bs[i].n ) )
+ {
+ *v = bs[i].v;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+typedef struct
+{
+ const char *n;
+ unsigned op;
+ int nr;
+ int c;
+ int r;
+} optab_t;
+static const optab_t ops[] =
+{
+ { "NOP", CPU_OP_NOP, 0, 0, 0 },
+ { "HALT", CPU_OP_HALT, 0, 0, 0 },
+ { "END", CPU_OP_END, 0, 0, 0 },
+ { "SYSCALL", CPU_OP_SYSCALL, 0, 0, 0 },
+ { "CLRCPUF", CPU_OP_CLRCPUF, 0, 0, 0 },
+ { "CLRMPUF", CPU_OP_CLRMPUF, 0, 0, 0 },
+ { "CLR", CPU_OP_CLR, 1, 0, 0 },
+ { "MOV", CPU_OP_MOV, 2, 0, 0 },
+ { "SWAP", CPU_OP_SWAP, 2, 0, 0 },
+ { "BSWAP", CPU_OP_BSWAP, 2, 0, 1 },
+ { "PUSH", CPU_OP_PUSH, 1, 0, 0 },
+ { "POP", CPU_OP_POP, 1, 0, 0 },
+ { "PUSHFP", CPU_OP_PUSHFP, 0, 0, 0 },
+ { "SETFP", CPU_OP_SETFP, 0, 0, 0 },
+ { "RESTSP", CPU_OP_RESTSP, 0, 0, 0 },
+ { "POPFP", CPU_OP_POPFP, 0, 0, 0 },
+ { "IADD", CPU_OP_IADD, 3, 0, 1 },
+ { "IADC", CPU_OP_IADC, 3, 0, 1 },
+ { "ISUB", CPU_OP_ISUB, 3, 0, 1 },
+ { "ISBB", CPU_OP_ISBB, 3, 0, 1 },
+ { "IAND", CPU_OP_IAND, 3, 0, 1 },
+ { "IOR", CPU_OP_IOR, 3, 0, 1 },
+ { "IXOR", CPU_OP_IXOR, 3, 0, 1 },
+ { "INOT", CPU_OP_INOT, 2, 0, 1 },
+ { "INEG", CPU_OP_INEG, 2, 0, 1 },
+ { "ICMP", CPU_OP_ICMP, 2, 0, 1 },
+ { "ITEST", CPU_OP_ITEST, 2, 0, 1 },
+ { "ISHL", CPU_OP_ISHL, 2, 0, 1 },
+ { "ISHR", CPU_OP_ISHR, 2, 0, 1 },
+ { "ISAL", CPU_OP_ISAL, 2, 0, 1 },
+ { "ISAR", CPU_OP_ISAR, 2, 0, 1 },
+ { "IROL", CPU_OP_IROL, 2, 0, 1 },
+ { "IROR", CPU_OP_IROR, 2, 0, 1 },
+ { "IRCL", CPU_OP_IRCL, 2, 0, 1 },
+ { "IRCR", CPU_OP_IRCR, 2, 0, 1 },
+ { "IINC", CPU_OP_IINC, 2, 0, 1 },
+ { "IDEC", CPU_OP_IDEC, 2, 0, 1 },
+ { "IXCHG", CPU_OP_IXCHG, 2, 0, 1 },
+ { "ICPY", CPU_OP_ICPY, 2, 0, 1 },
+ { "ICVT", CPU_OP_ICVT, 2, 0, 1 },
+ { "IMUL", CPU_OP_IMUL, 3, 0, 1 },
+ { "ISMUL", CPU_OP_ISMUL, 3, 0, 1 },
+ { "IDIV", CPU_OP_IDIV, 4, 0, 1 },
+ { "ISDIV", CPU_OP_ISDIV, 4, 0, 1 },
+ { "IBITSLEN", CPU_OP_IBITSLEN, 2, 0, 1 },
+ { "RADD", CPU_OP_RADD, 3, 0, 1 },
+ { "RSUB", CPU_OP_RSUB, 3, 0, 1 },
+ { "RMUL", CPU_OP_RMUL, 3, 0, 1 },
+ { "RDIV", CPU_OP_RDIV, 3, 0, 1 },
+ { "RSQRT", CPU_OP_RSQRT, 2, 0, 1 },
+ { "RSIN", CPU_OP_RSIN, 2, 0, 1 },
+ { "RCOS", CPU_OP_RCOS, 2, 0, 1 },
+ { "RTAN", CPU_OP_RTAN, 2, 0, 1 },
+ { "RASIN", CPU_OP_RASIN, 2, 0, 1 },
+ { "RACOS", CPU_OP_RACOS, 2, 0, 1 },
+ { "RATAN", CPU_OP_RATAN, 2, 0, 1 },
+ { "RLOG", CPU_OP_RLOG, 2, 0, 1 },
+ { "RLOG10", CPU_OP_RLOG10, 2, 0, 1 },
+ { "RLOG2", CPU_OP_RLOG2, 2, 0, 1 },
+ { "REXP", CPU_OP_REXP, 2, 0, 1 },
+ { "RSINH", CPU_OP_RSINH, 2, 0, 1 },
+ { "RCOSH", CPU_OP_RCOSH, 2, 0, 1 },
+ { "RTANH", CPU_OP_RTANH, 2, 0, 1 },
+ { "RPOW", CPU_OP_RPOW, 3, 0, 1 },
+ { "RHYPOT", CPU_OP_RHYPOT, 3, 0, 1 },
+ { "RATAN2", CPU_OP_RATAN2, 3, 0, 1 },
+ { "RNEG", CPU_OP_RNEG, 2, 0, 1 },
+ { "RABS", CPU_OP_RABS, 2, 0, 1 },
+ { "RCPY", CPU_OP_RCPY, 2, 0, 1 },
+ { "RCVT", CPU_OP_RCVT, 2, 0, 1 },
+ { "RFLOOR", CPU_OP_RFLOOR, 2, 0, 1 },
+ { "RCEIL", CPU_OP_RCEIL, 2, 0, 1 },
+ { "RROUND", CPU_OP_RROUND, 2, 0, 1 },
+ { "RFREXP", CPU_OP_RFREXP, 3, 0, 1 },
+ { "RLDEXP", CPU_OP_RLDEXP, 3, 0, 1 },
+ { "RLOGB", CPU_OP_RLOGB, 2, 0, 1 },
+ { "RMODF", CPU_OP_RMODF, 3, 0, 1 },
+ { "RREMAIN", CPU_OP_RREMAIN, 3, 0, 1 },
+ { "RLOG1P", CPU_OP_RLOG1P, 2, 0, 1 },
+ { "REXPM1", CPU_OP_REXPM1, 2, 0, 1 },
+ { "RASINH", CPU_OP_RASINH, 2, 0, 1 },
+ { "RACOSH", CPU_OP_RACOSH, 2, 0, 1 },
+ { "RATANH", CPU_OP_RATANH, 2, 0, 1 },
+ { "ITOR", CPU_OP_ITOR, 2, 0, 1 },
+ { "UITOR", CPU_OP_UITOR, 2, 0, 1 },
+ { "RTOI", CPU_OP_RTOI, 3, 0, 1 },
+ { "RTOUI", CPU_OP_RTOUI, 3, 0, 1 },
+ { "CADD", CPU_OP_CADD, 3, 1, 0 },
+ { "CSUB", CPU_OP_CSUB, 3, 1, 0 },
+ { "CMUL", CPU_OP_CMUL, 3, 1, 0 },
+ { "CDIV", CPU_OP_CDIV, 3, 1, 0 },
+ { "CEXP", CPU_OP_CEXP, 2, 1, 0 },
+ { "CSIN", CPU_OP_CSIN, 2, 1, 0 },
+ { "CCOS", CPU_OP_CCOS, 2, 1, 0 },
+ { "CSINH", CPU_OP_CSINH, 2, 1, 0 },
+ { "CCOSH", CPU_OP_CCOSH, 2, 1, 0 },
+ { "CLOG", CPU_OP_CLOG, 2, 1, 0 },
+ { "CSQRT", CPU_OP_CSQRT, 2, 1, 0 },
+ { "CPOW", CPU_OP_CPOW, 3, 1, 0 },
+ { "CCONJ", CPU_OP_CCONJ, 2, 1, 0 },
+ { "CCPY", CPU_OP_CCPY, 2, 1, 0 },
+ { "CCVT", CPU_OP_CCVT, 2, 1, 0 }
+};
+
+static int
+emit_instruction( buf_t *c, fixups_t *df, fixups_t *cf, fixups_t *pf, equs_t *eqs, char *mn, char *rest, char *err, size_t es )
+{
+ char *av[4];
+ int ac = rest && *trim( rest ) ? splitargs( rest, av, 4 ) : 0;
+ size_t i;
+ unsigned k;
+ for( i = 0; i < sizeof( ops ) / sizeof( ops[0] ); i++ ) if( streqi( mn, ops[i].n ) )
+ {
+ buf_t tmp =
+ {
+ 0
+ };
+ if( ac != ops[i].nr )
+ {
+ snprintf( err, es, "wrong operand count for %s", mn );
+ return( -1 );
+ }
+ if( put16( &tmp, ops[i].op ) ) return( -1 );
+ for( k = 0; k < (unsigned)ac; k++ ) if( preg( &tmp, av[k], ops[i].c, ops[i].r, eqs, err, es ) )
+ {
+ free( tmp.p );
+ return( -1 );
+ }
+ if( put( c, tmp.p, tmp.n ) )
+ {
+ free( tmp.p );
+ return( -1 );
+ }
+ free( tmp.p );
+ return( 0 );
+ }
+ if( streqi( mn, "SETF" ) || streqi( mn, "CLRF" ) )
+ {
+ unsigned bit;
+ if( ac != 1 || pswbitid( av[0], &bit ) || bit > CPU_PSWBIT_WRITE_MAX )
+ {
+ snprintf( err, es, "%s requires one writable PSW flag in range AF..INXF", mn );
+ return( -1 );
+ }
+ if( put16( c, streqi( mn, "SETF" ) ? CPU_OP_SETF : CPU_OP_CLRF ) ||
+ put8( c, bit ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "EXIT" ) )
+ {
+ cpu_register r;
+ int ic;
+ if( ac != 1 || regparse( av[0], &r, &ic, eqs, err, es ) || ic || r.bits != 32 )
+ {
+ snprintf( err, es, "EXIT requires one r32 status operand" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_EXIT ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "LDR" ) )
+ {
+ __mpu_real64_t v;
+ mpu_real64_t xv;
+ __mpu_uint64_t u;
+ cpu_register r;
+ int ic, ex, nb;
+ if( ac != 2 || regparse( av[0], &r, &ic, eqs, err, es ) || ic )
+ {
+ snprintf( err, es, "bad LDR destination" );
+ return( -1 );
+ }
+ if( r.bits < 32 )
+ {
+ snprintf( err, es, "LDR requires r32 or wider" );
+ return( -1 );
+ }
+ nb = ( r.bits == 32 ) ? NB_R32 : NB_R64;
+ if( nb == NB_R32 )
+ {
+ mpu_real32_t x32;
+ ex = real_literal_exact_value( av[1], NB_R32, ( mpu_real * ) x32 );
+ if( ex == 1 ) r_cvt( ( mpu_real * ) xv, ( mpu_real * ) x32, NB_R64, NB_R32 );
+ }
+ else ex = real_literal_exact_value( av[1], NB_R64, ( mpu_real * ) xv );
+ if( ex < 0 )
+ {
+ snprintf( err, es, "bad real immediate" );
+ return( -1 );
+ }
+ if( !ex )
+ {
+ snprintf( err, es, "LDR immediate is not exactly representable as real%d;\nuse .real <bits>, value in DATA and LOAD", r.bits == 32 ? 32 : 64 );
+ return( -1 );
+ }
+ memcpy( &v, xv, NB_R64 );
+ if( put16( c, CPU_OP_LDR ) || preg( c, av[0], 0, 1, eqs, err, es ) ) return( -1 );
+ memcpy( &u, &v, 8 );
+ return( put64( c, u ) );
+ }
+ if( streqi( mn, "LDC" ) )
+ {
+ float r, j;
+ __mpu_uint32_t ur, ui;
+ __mpu_uint64_t u;
+ cpu_register rg;
+ int ic, cr;
+ if( ac != 2 || regparse( av[0], &rg, &ic, eqs, err, es ) || !ic )
+ {
+ snprintf( err, es, "bad LDC destination" );
+ return( -1 );
+ }
+ cr = cimm_exact( av[1], &r, &j, err, es );
+ if( cr == -1 )
+ {
+ snprintf( err, es, "bad complex immediate" );
+ return( -1 );
+ }
+ if( cr == -2 ) return( -1 );
+ memcpy( &ur, &r, 4 );
+ memcpy( &ui, &j, 4 );
+ u = ( (__mpu_uint64_t)ur << 32 ) | ui;
+ if( put16( c, CPU_OP_LDC ) || preg( c, av[0], 1, 0, eqs, err, es ) || put64( c, u ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "RCONST" ) )
+ {
+ if( ac != 2 || constid( av[1], &k ) )
+ {
+ snprintf( err, es, "bad RCONST" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_RCONST ) || preg( c, av[0], 0, 1, eqs, err, es ) || put8( c, k ) ) return( -1 );
+ return( 0 );
+ }
+
+ if( streqi( mn, "ISHLN" ) || streqi( mn, "ISHRN" ) || streqi( mn, "ISALN" ) || streqi( mn, "ISARN" ) ||
+ streqi( mn, "IROLN" ) || streqi( mn, "IRORN" ) || streqi( mn, "IRCLN" ) || streqi( mn, "IRCRN" ))
+ {
+ unsigned op;
+ if( ac != 3 )
+ {
+ snprintf( err, es, "%s requires dst, src, r64 count", mn );
+ return( -1 );
+ }
+ if( streqi( mn, "ISHLN" ) ) op = CPU_OP_ISHLN;
+ else if( streqi( mn, "ISHRN" ) ) op = CPU_OP_ISHRN;
+ else if( streqi( mn, "ISALN" ) ) op = CPU_OP_ISALN;
+ else if( streqi( mn, "ISARN" ) ) op = CPU_OP_ISARN;
+ else if( streqi( mn, "IROLN" ) ) op = CPU_OP_IROLN;
+ else if( streqi( mn, "IRORN" ) ) op = CPU_OP_IRORN;
+ else if( streqi( mn, "IRCLN" ) ) op = CPU_OP_IRCLN;
+ else op = CPU_OP_IRCRN;
+ if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) ||
+ preg_r64( c, av[2], "shift count", eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "RCMP" ) )
+ {
+ if( ac != 3 )
+ {
+ snprintf( err, es, "RCMP requires r64 result, real a, real b" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_RCMP ) || preg_r64( c, av[0], "RCMP result", eqs, err, es ) ||
+ preg( c, av[1], 0, 1, eqs, err, es ) || preg( c, av[2], 0, 1, eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "RISNEG" ) || streqi( mn, "RSIGN" ) || streqi( mn, "RISIND" ) ||
+ streqi( mn, "RISNAN" ) || streqi( mn, "RISNANMAX" ) || streqi( mn, "RISNANMIN" ) ||
+ streqi( mn, "RISINF" ) || streqi( mn, "RISSIGNULL" ))
+ {
+ unsigned op;
+ if( ac != 2 )
+ {
+ snprintf( err, es, "%s requires r64 result, real source", mn );
+ return( -1 );
+ }
+ if( streqi( mn, "RISNEG" ) ) op = CPU_OP_RISNEG;
+ else if( streqi( mn, "RSIGN" ) ) op = CPU_OP_RSIGN;
+ else if( streqi( mn, "RISIND" ) ) op = CPU_OP_RISIND;
+ else if( streqi( mn, "RISNAN" ) ) op = CPU_OP_RISNAN;
+ else if( streqi( mn, "RISNANMAX" ) ) op = CPU_OP_RISNANMAX;
+ else if( streqi( mn, "RISNANMIN" ) ) op = CPU_OP_RISNANMIN;
+ else if( streqi( mn, "RISINF" ) ) op = CPU_OP_RISINF;
+ else op = CPU_OP_RISSIGNULL;
+ if( put16( c, op ) || preg_r64( c, av[0], "real predicate result", eqs, err, es ) ||
+ preg( c, av[1], 0, 1, eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "CREAL" ) || streqi( mn, "CIMAG" ) || streqi( mn, "CABS" ) ||
+ streqi( mn, "CNORM" ) || streqi( mn, "CARG" ))
+ {
+ unsigned op;
+ if( ac != 2 )
+ {
+ snprintf( err, es, "%s requires real destination, complex source", mn );
+ return( -1 );
+ }
+ if( streqi( mn, "CREAL" ) ) op = CPU_OP_CREAL;
+ else if( streqi( mn, "CIMAG" ) ) op = CPU_OP_CIMAG;
+ else if( streqi( mn, "CABS" ) ) op = CPU_OP_CABS;
+ else if( streqi( mn, "CNORM" ) ) op = CPU_OP_CNORM;
+ else op = CPU_OP_CARG;
+ if( put16( c, op ) || preg( c, av[0], 0, 1, eqs, err, es ) || preg( c, av[1], 1, 0, eqs, err, es ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "CRTOC" ) )
+ {
+ if( ac != 2 )
+ {
+ snprintf( err, es, "CRTOC requires complex destination, real source" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_CRTOC ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "CGEN" ) || streqi( mn, "CPOLAR" ) )
+ {
+ unsigned op = streqi( mn, "CGEN" ) ? CPU_OP_CGEN : CPU_OP_CPOLAR;
+ if( ac != 3 )
+ {
+ snprintf( err, es, "%s requires complex destination, two real sources", mn );
+ return( -1 );
+ }
+ if( put16( c, op ) || preg( c, av[0], 1, 0, eqs, err, es ) || preg( c, av[1], 0, 1, eqs, err, es ) ||
+ preg( c, av[2], 0, 1, eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "CISEQ" ) || streqi( mn, "CISNE" ) )
+ {
+ unsigned op = streqi( mn, "CISEQ" ) ? CPU_OP_CISEQ : CPU_OP_CISNE;
+ if( ac != 3 )
+ {
+ snprintf( err, es, "%s requires r64 result, complex a, complex b", mn );
+ return( -1 );
+ }
+ if( put16( c, op ) || preg_r64( c, av[0], "complex predicate result", eqs, err, es ) ||
+ preg( c, av[1], 1, 0, eqs, err, es ) || preg( c, av[2], 1, 0, eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "LOAD" ) || streqi( mn, "STORE" ) )
+ {
+ int store = streqi( mn, "STORE" );
+ if( ac != 2 )
+ {
+ snprintf( err, es, "wrong operand count" );
+ return( -1 );
+ }
+ if( put16( c, store ? CPU_OP_STORE : CPU_OP_LOAD ) ) return( -1 );
+ if( store )
+ {
+ if( addfix( df, c->n, av[0] ) || put64( c, 0 ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 );
+ }
+ else
+ {
+ if( preg( c, av[0], 0, 0, eqs, err, es ) || addfix( df, c->n, av[1] ) || put64( c, 0 ) ) return( -1 );
+ }
+ return( 0 );
+ }
+ if( streqi( mn, "LEA" ) )
+ {
+ if( ac != 2 )
+ {
+ snprintf( err, es, "LEA requires r64 destination and pointer expression" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_LEA ) || preg_ptr( c, av[0], eqs, err, es ) || addfix( pf, c->n, av[1] ) || put64( c, 0 ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "LEASP" ) )
+ {
+ __mpu_int64_t off;
+ if( ac != 2 )
+ {
+ snprintf( err, es, "LEASP requires r64 destination and signed SP offset" );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( av[1], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "LEASP offset must fit signed 64 bits" );
+ return( -1 );
+ }
+ }
+ if( put16( c, CPU_OP_LEASP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "LEAFP" ) )
+ {
+ __mpu_int64_t off;
+ if( ac != 2 )
+ {
+ snprintf( err, es, "LEAFP requires r64 destination and signed FP offset" );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( av[1], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "LEAFP offset must fit signed 64 bits" );
+ return( -1 );
+ }
+ }
+ if( put16( c, CPU_OP_LEAFP ) || preg_ptr( c, av[0], eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "LOADP" ) || streqi( mn, "STOREP" ) )
+ {
+ int store = streqi( mn, "STOREP" );
+ if( ac != 2 )
+ {
+ snprintf( err, es, "%s requires two register operands", mn );
+ return( -1 );
+ }
+ if( put16( c, store ? CPU_OP_STOREP : CPU_OP_LOADP ) ) return( -1 );
+ if( store )
+ {
+ if( preg_ptr( c, av[0], eqs, err, es ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 );
+ }
+ else
+ {
+ if( preg( c, av[0], 0, 0, eqs, err, es ) || preg_ptr( c, av[1], eqs, err, es ) ) return( -1 );
+ }
+ return( 0 );
+ }
+ if( streqi( mn, "PADD" ) || streqi( mn, "PSUB" ) || streqi( mn, "PDIFF" ) )
+ {
+ unsigned op = streqi( mn, "PADD" ) ? CPU_OP_PADD : ( streqi( mn, "PSUB" ) ? CPU_OP_PSUB : CPU_OP_PDIFF );
+ if( ac != 3 )
+ {
+ snprintf( err, es, "%s requires three r64 operands", mn );
+ return( -1 );
+ }
+ if( put16( c, op ) ||
+ preg_r64( c, av[0], mn, eqs, err, es ) ||
+ preg_r64( c, av[1], mn, eqs, err, es ) ||
+ preg_r64( c, av[2], mn, eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "PADDI" ) || streqi( mn, "PSUBI" ) )
+ {
+ __mpu_int64_t off;
+ unsigned op = streqi( mn, "PADDI" ) ? CPU_OP_PADDI : CPU_OP_PSUBI;
+ if( ac != 3 )
+ {
+ snprintf( err, es, "%s requires r64 destination, r64 PTR64 source and signed OFF64", mn );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( av[2], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "%s offset must fit signed 64 bits", mn );
+ return( -1 );
+ }
+ }
+ if( put16( c, op ) ||
+ preg_r64( c, av[0], mn, eqs, err, es ) ||
+ preg_r64( c, av[1], mn, eqs, err, es ) ||
+ put64( c, (__mpu_uint64_t)off )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "BMOV" ) )
+ {
+ if( ac != 3 )
+ {
+ snprintf( err, es, "BMOV requires r64 destination PTR64, r64 source PTR64 and r64 byte count" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_BMOV ) ||
+ preg_ptr( c, av[0], eqs, err, es ) ||
+ preg_ptr( c, av[1], eqs, err, es ) ||
+ preg_r64( c, av[2], "BMOV byte count", eqs, err, es )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "BMOVI" ) )
+ {
+ __mpu_uint64_t count;
+ if( ac != 3 )
+ {
+ snprintf( err, es, "BMOVI requires r64 destination PTR64, r64 source PTR64 and unsigned IMM64 byte count" );
+ return( -1 );
+ }
+ {
+ int er = eval_u64( av[2], eqs, 0, &count, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "BMOVI byte count must fit unsigned 64 bits" );
+ return( -1 );
+ }
+ }
+ if( put16( c, CPU_OP_BMOVI ) ||
+ preg_ptr( c, av[0], eqs, err, es ) ||
+ preg_ptr( c, av[1], eqs, err, es ) ||
+ put64( c, count )) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "JMPP" ) || streqi( mn, "CALLP" ) )
+ {
+ unsigned op = streqi( mn, "CALLP" ) ? CPU_OP_CALLP : CPU_OP_JMPP;
+ if( ac != 1 )
+ {
+ snprintf( err, es, "%s requires one r64 PROGRAM PTR64 operand", mn );
+ return( -1 );
+ }
+ if( put16( c, op ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "JMP" ) || streqi( mn, "CALL" ) )
+ {
+ int call = streqi( mn, "CALL" );
+ if( ac != 1 )
+ {
+ snprintf( err, es, "%s requires one CODE label", mn );
+ return( -1 );
+ }
+ if( put16( c, call ? CPU_OP_CALL : CPU_OP_JMP ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "RET" ) )
+ {
+ if( ac != 0 )
+ {
+ snprintf( err, es, "RET takes no operands" );
+ return( -1 );
+ }
+ return( put16( c, CPU_OP_RET ) );
+ }
+ if( streqi( mn, "JSET" ) || streqi( mn, "JCLR" ) )
+ {
+ unsigned bit;
+ if( ac != 2 || pswbitid( av[0], &bit ) )
+ {
+ snprintf( err, es, "bad PSW bit for %s", mn );
+ return( -1 );
+ }
+ if( put16( c, streqi( mn, "JSET" ) ? CPU_OP_JSET : CPU_OP_JCLR ) ||
+ put8( c, bit ) || addfix( cf, c->n, av[1] ) || put64( c, 0 )) return( -1 );
+ return( 0 );
+ }
+ {
+ struct A
+ {
+ const char *n;
+ unsigned op;
+ unsigned bit;
+ }
+ as[] =
+ {
+ { "JZ", CPU_OP_JSET, CPU_PSWBIT_ZF },
+ { "JNZ", CPU_OP_JCLR, CPU_PSWBIT_ZF },
+ { "JC", CPU_OP_JSET, CPU_PSWBIT_CF },
+ { "JNC", CPU_OP_JCLR, CPU_PSWBIT_CF },
+ { "JO", CPU_OP_JSET, CPU_PSWBIT_OF },
+ { "JNO", CPU_OP_JCLR, CPU_PSWBIT_OF },
+ { "JS", CPU_OP_JSET, CPU_PSWBIT_SF },
+ { "JNS", CPU_OP_JCLR, CPU_PSWBIT_SF }
+ };
+ size_t ai;
+ for( ai = 0; ai < sizeof( as ) / sizeof( as[0] ); ai++ ) if( streqi( mn, as[ai].n ) )
+ {
+ if( ac != 1 )
+ {
+ snprintf( err, es, "%s requires one CODE label", mn );
+ return( -1 );
+ }
+ if( put16( c, as[ai].op ) || put8( c, as[ai].bit ) || addfix( cf, c->n, av[0] ) || put64( c, 0 ) ) return( -1 );
+ return( 0 );
+ }
+ }
+ if( streqi( mn, "LDSP" ) || streqi( mn, "STSP" ) )
+ {
+ __mpu_int64_t off;
+ int store = streqi( mn, "STSP" );
+ if( ac != 2 )
+ {
+ snprintf( err, es, "%s requires two operands", mn );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "%s offset must fit signed 64 bits", mn );
+ return( -1 );
+ }
+ }
+ if( put16( c, store ? CPU_OP_STSP : CPU_OP_LDSP ) ) return( -1 );
+ if( store )
+ {
+ if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 );
+ }
+ else
+ {
+ if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 );
+ }
+ return( 0 );
+ }
+ if( streqi( mn, "LDFP" ) || streqi( mn, "STFP" ) )
+ {
+ __mpu_int64_t off;
+ int store = streqi( mn, "STFP" );
+ if( ac != 2 )
+ {
+ snprintf( err, es, "%s requires two operands", mn );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( store ? av[0] : av[1], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "%s offset must fit signed 64 bits", mn );
+ return( -1 );
+ }
+ }
+ if( put16( c, store ? CPU_OP_STFP : CPU_OP_LDFP ) ) return( -1 );
+ if( store )
+ {
+ if( put64( c, (__mpu_uint64_t)off ) || preg( c, av[1], 0, 0, eqs, err, es ) ) return( -1 );
+ }
+ else
+ {
+ if( preg( c, av[0], 0, 0, eqs, err, es ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 );
+ }
+ return( 0 );
+ }
+ if( streqi( mn, "ADJSP" ) )
+ {
+ __mpu_int64_t off;
+ if( ac != 1 )
+ {
+ snprintf( err, es, "ADJSP requires one signed byte offset" );
+ return( -1 );
+ }
+ {
+ int er = eval_i64( av[0], eqs, 0, &off, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 )
+ {
+ snprintf( err, es, "ADJSP offset must fit signed 64 bits" );
+ return( -1 );
+ }
+ }
+ if( put16( c, CPU_OP_ADJSP ) || put64( c, (__mpu_uint64_t)off ) ) return( -1 );
+ return( 0 );
+ }
+ if( streqi( mn, "SETSP" ) )
+ {
+ if( ac != 1 )
+ {
+ snprintf( err, es, "SETSP requires one r64 PTR64 operand" );
+ return( -1 );
+ }
+ if( put16( c, CPU_OP_SETSP ) || preg_ptr( c, av[0], eqs, err, es ) ) return( -1 );
+ return( 0 );
+ }
+ snprintf( err, es, "unknown mnemonic: %s", mn );
+ return( -1 );
+}
+
+/* Special LDI emitter kept separate to make signed 64-bit parsing explicit. */
+static int
+emit_ldi( buf_t *c, equs_t *eqs, char *rest, char *err, size_t es )
+{
+ char *av[3];
+ __mpu_int64_t v;
+ __int128 xv;
+ cpu_register r;
+ int ic, ac = splitargs( rest, av, 3 );
+ if( ac != 2 )
+ {
+ snprintf( err, es, "wrong operand count for LDI" );
+ return( -1 );
+ }
+ if( regparse( av[0], &r, &ic, eqs, err, es ) || ic )
+ {
+ snprintf( err, es, "bad LDI destination" );
+ return( -1 );
+ }
+ if( eval_expression( av[1], eqs, 0, &xv, err, es ) )
+ {
+ snprintf( err, es, "bad integer immediate: %s", av[1] );
+ return( -1 );
+ }
+ if( xv < ( __int128 ) INT64_MIN || xv > ( __int128 ) INT64_MAX )
+ {
+ snprintf( err, es, "LDI immediate is not exactly representable as integer64;\nuse .integer <bits>, value in DATA and LOAD" );
+ return( -1 );
+ }
+ v = (__mpu_int64_t)xv;
+ if( r.bits < 64 )
+ {
+ __mpu_int64_t lo = - ( (__mpu_int64_t)1 << ( r.bits - 1 ) ), hi = ( (__mpu_int64_t)1 << ( r.bits - 1 ) ) - 1;
+ if( v < lo || v > hi )
+ {
+ snprintf( err, es, "LDI immediate is not exactly representable as integer%u;\nuse .integer <bits>, value in DATA and LOAD", r.bits );
+ return( -1 );
+ }
+ }
+ if( put16( c, CPU_OP_LDI ) || preg( c, av[0], 0, 1, eqs, err, es ) || put64( c, (__mpu_uint64_t)v ) ) return( -1 );
+ return( 0 );
+}
+
+static int datadir( buf_t *d, char *line, fixups_t *cptr_fix, fixups_t *ptr_fix,
+ __mpu_uint64_t *section_align, equs_t *eqs, char *err, size_t es)
+{
+ char *p = trim( line ), *mn = p, *rest, *av[256];
+ int ac, bits, nb;
+ __mpu_uint64_t uv;
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+
+ if( streqi( mn, ".align" ) )
+ {
+ if( !*rest )
+ {
+ snprintf( err, es, ".align requires a value" );
+ return( -1 );
+ }
+ {
+ int er = eval_u64( rest, eqs, 0, &uv, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || !uv || uv > (__mpu_uint64_t)SIZE_MAX )
+ {
+ snprintf( err, es, "bad .align" );
+ return( -1 );
+ }
+ }
+ if( section_align && uv > *section_align ) * section_align = uv;
+ while( d->n % (size_t)uv ) if( put8( d, 0 ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".utf8" ) || streqi( mn, ".ucs2" ) )
+ {
+ buf_t text =
+ {
+ 0
+ };
+ if( !*rest )
+ {
+ snprintf( err, es, "%s requires a quoted UTF-8 string", mn );
+ return( -1 );
+ }
+ if( parse_utf8_string( rest, &text, err, es ) ) return( -1 );
+ if( streqi( mn, ".utf8" ) )
+ {
+ if( put( d, text.p, text.n ) )
+ {
+ free( text.p );
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ }
+ else
+ {
+ __mpu_size_t nch = mpu_utf8_to_ucs2( 0, (const __mpu_char8_t *)text.p, 0 ), i;
+ __mpu_char16_t *ucs;
+ if( nch == (__mpu_size_t)-1 )
+ {
+ free( text.p );
+ snprintf( err, es, ".ucs2 string is not representable as strict UCS-2" );
+ return( -1 );
+ }
+ ucs = (__mpu_char16_t *)calloc( (size_t)nch + 1, sizeof( *ucs ) );
+ if( !ucs )
+ {
+ free( text.p );
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ if( mpu_utf8_to_ucs2( ucs, (const __mpu_char8_t *)text.p, nch + 1 ) != nch )
+ {
+ free( ucs );
+ free( text.p );
+ snprintf( err, es, "UTF-8 to UCS-2 conversion failed" );
+ return( -1 );
+ }
+ for( i = 0; i < nch; i++ )
+ {
+ __mpu_UNIT_t x[2];
+ __cpu_data_put_u16( x, (__mpu_uint16_t)ucs[i] );
+ if( put( d, x, 2 ) )
+ {
+ free( ucs );
+ free( text.p );
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ }
+ {
+ __mpu_UNIT_t z[2];
+ __cpu_data_put_u16( z, 0 );
+ if( put( d, z, 2 ) )
+ {
+ free( ucs );
+ free( text.p );
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ }
+ free( ucs );
+ }
+ free( text.p );
+ return( 0 );
+ }
+
+ if( !*rest )
+ {
+ snprintf( err, es, "data directive requires operands" );
+ return( -1 );
+ }
+ ac = splitargs( rest, av, 256 );
+ if( ac < 0 )
+ {
+ snprintf( err, es, "too many or malformed directive arguments" );
+ return( -1 );
+ }
+
+ if( streqi( mn, ".byte" ) )
+ {
+ int i;
+ if( ac < 1 )
+ {
+ snprintf( err, es, ".byte requires one or more values" );
+ return( -1 );
+ }
+ for( i = 0; i < ac; i++ )
+ {
+ int er = eval_u64( av[i], eqs, 0, &uv, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || uv > 255 )
+ {
+ snprintf( err, es, ".byte value must fit 8 bits: %s", av[i] );
+ return( -1 );
+ }
+ if( put8( d, (unsigned)uv ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".integer" ) || streqi( mn, ".int" ) )
+ {
+ int i;
+ mpu_wide_int_t x;
+ if( ac < 2 )
+ {
+ snprintf( err, es, "%s requires: bits, value[, value...]", mn );
+ return( -1 );
+ }
+ {
+ int er = eval_u64( av[0], eqs, 0, &uv, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || uv > 65536 )
+ {
+ snprintf( err, es, "bad .integer width" );
+ return( -1 );
+ }
+ }
+ bits = (int)uv;
+ nb = bits / 8;
+ if( bits < 8 || ( bits & ( bits - 1 ) ) )
+ {
+ snprintf( err, es, "bad .integer width" );
+ return( -1 );
+ }
+ for( i = 1; i < ac; i++ )
+ {
+ const char *value = av[i];
+ char dec[64];
+ __int128 ev;
+ memset( x, 0, sizeof( x ) );
+ if( !plain_integer_literal( value ) )
+ {
+ if( eval_expression( value, eqs, 0, &ev, err, es ) ) return( -1 );
+ i128_decimal( ev, dec );
+ value = dec;
+ }
+ iatoi( ( mpu_int * ) x, (__mpu_char8_t *)value, nb );
+ if( put( d, x, (size_t)nb ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".real" ) )
+ {
+ mpu_wide_real_t x;
+ if( ac != 2 )
+ {
+ snprintf( err, es, ".real requires: bits, value" );
+ return( -1 );
+ }
+ {
+ int er = eval_u64( av[0], eqs, 0, &uv, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || uv > 65536 )
+ {
+ snprintf( err, es, "bad .real width" );
+ return( -1 );
+ }
+ }
+ bits = (int)uv;
+ nb = bits / 8;
+ if( bits < 32 || ( bits & ( bits - 1 ) ) )
+ {
+ snprintf( err, es, "bad .real width" );
+ return( -1 );
+ }
+ memset( x, 0, sizeof( x ) );
+ if( ascii_to_real( ( mpu_real * ) x, (__mpu_char8_t *)av[1], nb ) < 0 )
+ {
+ snprintf( err, es, "bad .real constant" );
+ return( -1 );
+ }
+ if( put( d, x, (size_t)nb ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".complex" ) )
+ {
+ mpu_wide_complex_t x;
+ mpu_wide_real_t r, j;
+ if( ac != 3 )
+ {
+ snprintf( err, es, ".complex requires: physical-bits, real, imag" );
+ return( -1 );
+ }
+ {
+ int er = eval_u64( av[0], eqs, 0, &uv, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || uv > 65536 )
+ {
+ snprintf( err, es, "bad .complex physical width" );
+ return( -1 );
+ }
+ }
+ bits = (int)uv;
+ nb = bits / 8;
+ if( bits < 64 || ( bits & ( bits - 1 ) ) )
+ {
+ snprintf( err, es, "bad .complex physical width" );
+ return( -1 );
+ }
+ if( ascii_to_real( ( mpu_real * ) r, (__mpu_char8_t *)av[1], nb / 2 ) < 0 || ascii_to_real( ( mpu_real * ) j, (__mpu_char8_t *)av[2], nb / 2 ) < 0 )
+ {
+ snprintf( err, es, "bad .complex component" );
+ return( -1 );
+ }
+ c_gen_complex( ( mpu_complex * ) x, ( mpu_real * ) r, ( mpu_real * ) j, nb / 2 );
+ if( put( d, x, (size_t)nb ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".cptr" ) || streqi( mn, ".codeptr" ) )
+ {
+ __mpu_uint64_t zero = 0;
+ if( ac != 1 || !*av[0] )
+ {
+ snprintf( err, es, ".cptr requires one CODE expression" );
+ return( -1 );
+ }
+ if( addfix( cptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+
+ if( streqi( mn, ".ptr" ) )
+ {
+ __mpu_uint64_t zero = 0;
+ if( ac != 1 || !*av[0] )
+ {
+ snprintf( err, es, ".ptr requires one pointer expression" );
+ return( -1 );
+ }
+ if( addfix( ptr_fix, d->n, av[0] ) || put( d, &zero, CPU_PTR64_SIZE ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ return( 0 );
+ }
+
+ snprintf( err, es, "unknown data directive: %s", mn );
+ return( -1 );
+}
+
+static int
+valid_symbol_name( const char *s )
+{
+ if( !s || !symfirst( (unsigned char)*s ) ) return( 0 );
+ for( s++; *s; s++ ) if( !symrest( (unsigned char)*s ) ) return( 0 );
+ return( 1 );
+}
+
+static int
+finddecl( const symdecls_t *d, const char *name, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < d->n; i++ ) if( !strcmp( d->p[i].name, name ) )
+ {
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int adddecl( symdecls_t *d, const char *name, int is_global, int is_extern, int is_weak,
+ char *err, size_t es)
+{
+ size_t i;
+ if( !valid_symbol_name( name ) )
+ {
+ snprintf( err, es, "bad symbol name: %s", name );
+ return( -1 );
+ }
+ if( !strcmp( name, "_start_life" ) )
+ {
+ snprintf( err, es, "_start_life is an assembler pseudo-label and cannot be declared" );
+ return( -1 );
+ }
+ if( is_weak && ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) )
+ {
+ snprintf( err, es, "lifecycle symbol %s cannot be weak", name );
+ return( -1 );
+ }
+ if( !finddecl( d, name, &i ) )
+ {
+ if( is_global ) d->p[i].is_global = 1;
+ if( is_extern ) d->p[i].is_extern = 1;
+ if( is_weak ) d->p[i].is_weak = 1;
+ return( 0 );
+ }
+ if( grow( (void **)&d->p, &d->cap, d->n + 1, sizeof( *d->p ) ) )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ memset( &d->p[d->n], 0, sizeof( *d->p ) );
+ strcpy( d->p[d->n].name, name );
+ d->p[d->n].is_global = is_global;
+ d->p[d->n].is_extern = is_extern;
+ d->p[d->n].is_weak = is_weak;
+ d->n++;
+ return( 0 );
+}
+
+/* Return 1 for a declaration line, 0 otherwise, -1 on error. */
+static int
+collect_decl_line( char *line, symdecls_t *decls, int line_no, char *err, size_t es )
+{
+ char *p = trim( line ), *mn = p, *rest, *av[256];
+ int ac, i, is_global = 0, is_extern = 0, is_weak = 0;
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+ if( streqi( mn, ".global" ) || streqi( mn, ".globl" ) ) is_global = 1;
+ else
+ if( streqi( mn, ".extern" ) )
+ {
+ is_global = 1;
+ is_extern = 1;
+ }
+ else
+ if( streqi( mn, ".weak" ) )
+ {
+ is_weak = 1;
+ }
+ else return( 0 );
+ if( !*rest )
+ {
+ snprintf( err, es, "line %d: %s requires one or more symbol names", line_no, mn );
+ return( -1 );
+ }
+ ac = splitargs( rest, av, 256 );
+ if( ac < 1 )
+ {
+ snprintf( err, es, "line %d: malformed %s", line_no, mn );
+ return( -1 );
+ }
+ for( i = 0; i < ac; i++ ) if( adddecl( decls, av[i], is_global, is_extern, is_weak, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ return( -1 );
+ }
+ return( 1 );
+}
+
+/* .bss is SHT_NOBITS: only layout directives are legal and no payload
+ bytes are emitted into the relocatable object. */
+static int bssdir( size_t *bss_size, char *line, __mpu_uint64_t *section_align,
+ equs_t *eqs, char *err, size_t es)
+{
+ char *p = trim( line ), *mn = p, *rest;
+ __mpu_uint64_t v;
+
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+
+ if( streqi( mn, ".align" ) )
+ {
+ __mpu_uint64_t rem, add;
+ int er;
+ if( !*rest )
+ {
+ snprintf( err, es, ".align requires a value" );
+ return( -1 );
+ }
+ er = eval_u64( rest, eqs, 0, &v, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || !v || v > (__mpu_uint64_t)SIZE_MAX || ( v & ( v - 1 ) ) )
+ {
+ snprintf( err, es, "bad .align" );
+ return( -1 );
+ }
+ if( section_align && v > *section_align ) * section_align = v;
+ rem = (__mpu_uint64_t)( *bss_size ) % v;
+ add = rem ? v - rem : 0;
+ if( add > (__mpu_uint64_t)SIZE_MAX - *bss_size )
+ {
+ snprintf( err, es, ".bss size overflow" );
+ return( -1 );
+ }
+ *bss_size+=(size_t)add;
+ return( 0 );
+ }
+
+ if( streqi( mn, ".zero" ) )
+ {
+ int er;
+ if( !*rest )
+ {
+ snprintf( err, es, ".zero requires a byte count" );
+ return( -1 );
+ }
+ er = eval_u64( rest, eqs, 0, &v, err, es );
+ if( er < 0 ) return( -1 );
+ if( er > 0 || v > (__mpu_uint64_t)SIZE_MAX - *bss_size )
+ {
+ snprintf( err, es, "bad .zero byte count" );
+ return( -1 );
+ }
+ *bss_size+=(size_t)v;
+ return( 0 );
+ }
+
+ snprintf( err, es, "only .align and .zero are valid in .bss" );
+ return( -1 );
+}
+
+/* Return 1 when line is .equ, 0 when it is not, -1 on malformed .equ. */
+static int
+collect_equ_line( char *line, equs_t *eqs, int line_no, char *err, size_t es )
+{
+ char *p = trim( line ), *mn = p, *rest, *av[2];
+ int ac;
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+ if( !streqi( mn, ".equ" ) ) return( 0 );
+ if( !*rest )
+ {
+ snprintf( err, es, "line %d: .equ requires NAME, expression", line_no );
+ return( -1 );
+ }
+ ac = splitargs( rest, av, 2 );
+ if( ac != 2 || !valid_symbol_name( av[0] ) || !*av[1] )
+ {
+ snprintf( err, es, "line %d: .equ requires NAME, expression", line_no );
+ return( -1 );
+ }
+ if( addequ( eqs, av[0], av[1], line_no, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ return( -1 );
+ }
+ return( 1 );
+}
+
+enum asm_section
+{
+ ASM_SEC_NONE = 0,
+ ASM_SEC_TEXT,
+ ASM_SEC_DATA,
+ ASM_SEC_BSS,
+ ASM_SEC_INIT,
+ ASM_SEC_FINI,
+ ASM_SEC_CONTROL,
+ ASM_SEC_INPUT,
+ ASM_SEC_OUTPUT,
+ ASM_SEC_TRACE
+};
+
+static int label_in_any( const char *name, const labels_t *a, const labels_t *b,
+ const labels_t *c, const labels_t *d, const labels_t *e,
+ const labels_t *f)
+{
+ __mpu_uint64_t x;
+ return( a && !findlabel( a, name, &x ) ) || ( b && !findlabel( b, name, &x ) ) ||
+ ( c && !findlabel( c, name, &x ) ) || ( d && !findlabel( d, name, &x ) ) ||
+ ( e && !findlabel( e, name, &x ) ) || ( f && !findlabel( f, name, &x ) );
+}
+
+static int resolve_ptr_expression( const char *expr, equs_t *eqs,
+ const labels_t *code, const labels_t *data,
+ const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace,
+ const size_t sizes[8], __mpu_uint64_t *ptr,
+ char *err, size_t es)
+{
+ const labels_t *ls[6] =
+ {
+ data, control, input, output, trace, code
+ };
+ const unsigned segs[6] =
+ {
+ CPU_PTR_SEG_DATA, CPU_PTR_SEG_CONTROL, CPU_PTR_SEG_INPUT,
+ CPU_PTR_SEG_OUTPUT, CPU_PTR_SEG_TRACE, CPU_PTR_SEG_PROGRAM
+ };
+ __mpu_uint64_t value = 0;
+ int i, found = 0, er;
+ unsigned found_seg = CPU_PTR_SEG_DATA;
+ char tmp[256];
+
+ /* A pure absolute/.equ expression keeps historical LEA semantics: DATA. */
+ er = eval_u64( expr, eqs, 0, &value, tmp, sizeof( tmp ) );
+ if( er == 0 )
+ {
+ if( value > sizes[CPU_PTR_SEG_DATA] )
+ {
+ snprintf( err, es, "LEA DATA expression is outside DATA: %s", expr );
+ return( -1 );
+ }
+ return( __cpu_ptr_make( ptr, CPU_PTR_SEG_DATA, value ) );
+ }
+
+ for( i = 0; i < 6; i++ )
+ {
+ __mpu_uint64_t v;
+ if( eval_u64( expr, eqs, ls[i], &v, tmp, sizeof( tmp ) ) == 0 )
+ {
+ found++;
+ value = v;
+ found_seg = segs[i];
+ }
+ }
+ if( found != 1 )
+ {
+ snprintf( err, es, found ? "ambiguous pointer expression: %s" : "unknown symbol in pointer expression: %s", expr );
+ return( -1 );
+ }
+ if( value > sizes[found_seg] )
+ {
+ snprintf( err, es, "pointer expression is outside its segment: %s", expr );
+ return( -1 );
+ }
+ if( found_seg == CPU_PTR_SEG_HEAP && value < CPU_HEAP_BEGIN )
+ {
+ snprintf( err, es, "pointer expression enters reserved HEAP guard: %s", expr );
+ return( -1 );
+ }
+ if( __cpu_ptr_make( ptr, found_seg, value ) )
+ {
+ snprintf( err, es, "cannot encode PTR64 expression: %s", expr );
+ return( -1 );
+ }
+ return( 0 );
+}
+
+static int apply_cptr_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs,
+ const labels_t *clabels, size_t code_size,
+ char *err, size_t es)
+{
+ size_t i;
+ for( i = 0; i < fx->n; i++ )
+ {
+ __mpu_uint64_t a, ptr;
+ int er = eval_u64( fx->p[i].expr, eqs, clabels, &a, err, es );
+ if( er < 0 )
+ {
+ char cause[256];
+ snprintf( cause, sizeof( cause ), "%s", err );
+ snprintf( err, es, ".cptr expression '%s': %s", fx->p[i].expr, cause );
+ return( -1 );
+ }
+ if( er > 0 || a >= code_size )
+ {
+ snprintf( err, es, ".cptr expression does not address PROGRAM: %s", fx->p[i].expr );
+ return( -1 );
+ }
+ if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, a ) )
+ {
+ snprintf( err, es, "cannot encode PROGRAM PTR64 for: %s", fx->p[i].expr );
+ return( -1 );
+ }
+ __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr );
+ }
+ return( 0 );
+}
+
+static int apply_ptr_data_fixups( buf_t *dst, const fixups_t *fx, equs_t *eqs,
+ const labels_t *code, const labels_t *data,
+ const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace,
+ const size_t sizes[8], char *err, size_t es)
+{
+ size_t i;
+ for( i = 0; i < fx->n; i++ )
+ {
+ __mpu_uint64_t ptr;
+ if( resolve_ptr_expression( fx->p[i].expr, eqs, code, data, control, input, output, trace,
+ sizes, &ptr, err, es))
+ {
+ char cause[256];
+ snprintf( cause, sizeof( cause ), "%s", err );
+ snprintf( err, es, ".ptr expression '%s': %s", fx->p[i].expr, cause );
+ return( -1 );
+ }
+ __cpu_data_put_u64( dst->p + fx->p[i].pos, ptr );
+ }
+ return( 0 );
+}
+
+int
+cpu_assemble_text( const char *src, cpu_asm_image *im, char *err, size_t es )
+{
+ char *copy = 0, *scan = 0, *save, *line;
+ buf_t code =
+ {
+ 0
+ }
+ , data =
+ {
+ 0
+ }
+ , control =
+ {
+ 0
+ }
+ , input =
+ {
+ 0
+ }
+ , output =
+ {
+ 0
+ }
+ , trace =
+ {
+ 0
+ };
+ labels_t clabels =
+ {
+ 0
+ }
+ , dlabels =
+ {
+ 0
+ }
+ , ctlabels =
+ {
+ 0
+ }
+ , ilabels =
+ {
+ 0
+ }
+ , olabels =
+ {
+ 0
+ }
+ , tlabels =
+ {
+ 0
+ };
+ fixups_t dfix =
+ {
+ 0
+ }
+ , cfix =
+ {
+ 0
+ }
+ , pfix =
+ {
+ 0
+ };
+ fixups_t dcptr =
+ {
+ 0
+ }
+ , ccptr =
+ {
+ 0
+ }
+ , icptr =
+ {
+ 0
+ }
+ , ocptr =
+ {
+ 0
+ }
+ , tcptr =
+ {
+ 0
+ };
+ fixups_t dptr =
+ {
+ 0
+ }
+ , ctptr =
+ {
+ 0
+ }
+ , iptr =
+ {
+ 0
+ }
+ , optr =
+ {
+ 0
+ }
+ , tptr =
+ {
+ 0
+ };
+ __mpu_uint64_t dalign = 1, ctalign = 1, ialign = 1, oalign = 1, talign = 1;
+ equs_t eqs =
+ {
+ 0
+ };
+ int section = ASM_SEC_NONE, line_no = 0, rc = -1;
+ int init_seen = 0, text_seen = 0, fini_seen = 0, start_seen = 0, life_seen = 0, fini_entry_seen = 0;
+ __mpu_uint64_t first_text = 0, start_entry = 0, life_entry = 0, fini_entry = 0;
+
+ if( !src || !im ) return( -1 );
+ memset( im, 0, sizeof( *im ) );
+ if( !mpu_utf8valid( (const __mpu_char8_t *)src ) )
+ {
+ snprintf( err, es, "assembler source is not valid UTF-8" );
+ return( -1 );
+ }
+
+ /* Resolve absolute .equ symbols first. Section labels intentionally do not
+ participate in .equ, preserving the distinction between absolute symbols
+ and relocatable/segment-relative labels. */
+ scan = strdup( src );
+ if( !scan ) return( -1 );
+ for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) )
+ {
+ int er;
+ line_no++;
+ strip_comment( line );
+ if( !*trim( line ) ) continue;
+ er = collect_equ_line( line, &eqs, line_no, err, es );
+ if( er < 0 ) goto out;
+ }
+ free( scan );
+ scan = 0;
+ {
+ size_t i;
+ __int128 v;
+ for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ }
+ }
+
+ /* DATA keeps its ABI NULL guard. Other image-backed data spaces begin at
+ offset zero because their non-zero segment selector already distinguishes
+ them from the all-zero NULL PTR64. */
+ while( data.n < CPU_DATA_NULL_GUARD_SIZE )
+ if( put8( &data, 0 ) )
+ {
+ snprintf( err, es, "out of memory" );
+ goto out;
+ }
+
+ copy = strdup( src );
+ if( !copy ) goto out;
+ line_no = 0;
+ for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) )
+ {
+ char *p, *colon, *mn, *rest, *q;
+ buf_t *db = 0;
+ labels_t *dl = 0;
+ fixups_t *cpfx = 0, *pfx = 0;
+ __mpu_uint64_t *salign = 0;
+ line_no++;
+ strip_comment( line );
+ p = trim( line );
+ if( !*p ) continue;
+
+ q = p;
+ while( *q && !isspace( (unsigned char)*q ) ) q++;
+ if( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) continue;
+
+ if( streqi( p, ".text" ) )
+ {
+ if( !text_seen )
+ {
+ first_text = (__mpu_uint64_t)code.n;
+ text_seen = 1;
+ }
+ section = ASM_SEC_TEXT;
+ continue;
+ }
+ if( streqi( p, ".init" ) )
+ {
+ init_seen = 1;
+ section = ASM_SEC_INIT;
+ continue;
+ }
+ if( streqi( p, ".fini" ) )
+ {
+ fini_seen = 1;
+ section = ASM_SEC_FINI;
+ continue;
+ }
+ if( streqi( p, ".data" ) )
+ {
+ section = ASM_SEC_DATA;
+ continue;
+ }
+ if( streqi( p, ".control" ) )
+ {
+ section = ASM_SEC_CONTROL;
+ continue;
+ }
+ if( streqi( p, ".input" ) )
+ {
+ section = ASM_SEC_INPUT;
+ continue;
+ }
+ if( streqi( p, ".output" ) )
+ {
+ section = ASM_SEC_OUTPUT;
+ continue;
+ }
+ if( streqi( p, ".trace" ) )
+ {
+ section = ASM_SEC_TRACE;
+ continue;
+ }
+
+ colon = label_colon( p );
+ if( colon )
+ {
+ size_t ei;
+ char *name;
+ *colon = 0;
+ name = trim( p );
+ if( !valid_symbol_name( name ) )
+ {
+ snprintf( err, es, "line %d: bad label name", line_no );
+ goto out;
+ }
+ if( !findequ( &eqs, name, &ei ) )
+ {
+ snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name );
+ goto out;
+ }
+ if( label_in_any( name, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels ) )
+ {
+ snprintf( err, es, "line %d: duplicate label: %s", line_no, name );
+ goto out;
+ }
+ if( section == ASM_SEC_TEXT || section == ASM_SEC_INIT || section == ASM_SEC_FINI )
+ {
+ if( addlabel( &clabels, name, code.n ) )
+ {
+ snprintf( err, es, "line %d: bad/duplicate CODE label", line_no );
+ goto out;
+ }
+ if( !strcmp( name, "_start" ) )
+ {
+ if( section != ASM_SEC_INIT )
+ {
+ snprintf( err, es, "line %d: _start must be in .init", line_no );
+ goto out;
+ }
+ start_seen = 1;
+ start_entry = code.n;
+ }
+ if( !strcmp( name, "_life" ) )
+ {
+ if( section != ASM_SEC_TEXT )
+ {
+ snprintf( err, es, "line %d: _life must be in .text", line_no );
+ goto out;
+ }
+ life_seen = 1;
+ life_entry = code.n;
+ }
+ if( !strcmp( name, "_fini" ) )
+ {
+ if( section != ASM_SEC_FINI )
+ {
+ snprintf( err, es, "line %d: _fini must be in .fini", line_no );
+ goto out;
+ }
+ fini_entry_seen = 1;
+ fini_entry = code.n;
+ }
+ }
+ else
+ {
+ switch( section )
+ {
+ case ASM_SEC_DATA: dl = &dlabels;
+ break;
+ case ASM_SEC_CONTROL: dl = &ctlabels;
+ break;
+ case ASM_SEC_INPUT: dl = &ilabels;
+ break;
+ case ASM_SEC_OUTPUT: dl = &olabels;
+ break;
+ case ASM_SEC_TRACE: dl = &tlabels;
+ break;
+ default: break;
+ }
+ if( !dl || addlabel( dl, name, ( section == ASM_SEC_DATA ) ? data.n : ( section == ASM_SEC_CONTROL ) ? control.n : ( section == ASM_SEC_INPUT ) ? input.n : ( section == ASM_SEC_OUTPUT ) ? output.n : trace.n ) )
+ {
+ snprintf( err, es, "line %d: label outside a valid section or duplicate label", line_no );
+ goto out;
+ }
+ }
+ p = trim( colon + 1 );
+ if( !*p ) continue;
+ }
+
+ switch( section )
+ {
+ case ASM_SEC_DATA: db = &data;
+ cpfx = &dcptr;
+ pfx = &dptr;
+ salign = &dalign;
+ break;
+ case ASM_SEC_CONTROL: db = &control;
+ cpfx = &ccptr;
+ pfx = &ctptr;
+ salign = &ctalign;
+ break;
+ case ASM_SEC_INPUT: db = &input;
+ cpfx = &icptr;
+ pfx = &iptr;
+ salign = &ialign;
+ break;
+ case ASM_SEC_OUTPUT: db = &output;
+ cpfx = &ocptr;
+ pfx = &optr;
+ salign = &oalign;
+ break;
+ case ASM_SEC_TRACE: db = &trace;
+ cpfx = &tcptr;
+ pfx = &tptr;
+ salign = &talign;
+ break;
+ default:break;
+ }
+ if( db )
+ {
+ if( datadir( db, p, cpfx, pfx, salign, &eqs, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ } continue;
+ }
+ if( section != ASM_SEC_TEXT && section != ASM_SEC_INIT && section != ASM_SEC_FINI )
+ {
+ snprintf( err, es, "line %d: select a code or data section", line_no );
+ goto out;
+ }
+
+ mn = p;
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+ if( streqi( mn, "LDI" ) )
+ {
+ if( !*rest || emit_ldi( &code, &eqs, rest, err, es ) ) goto badline;
+ }
+ else
+ {
+ if( emit_instruction( &code, &dfix, &cfix, &pfix, &eqs, mn, rest, err, es ) ) goto badline;
+ } continue;
+badline:
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ }
+ }
+
+ if( init_seen && !start_seen )
+ {
+ snprintf( err, es, ".init section requires ABI entry label _start" );
+ goto out;
+ }
+ if( fini_seen && !fini_entry_seen )
+ {
+ snprintf( err, es, ".fini section requires ABI entry label _fini" );
+ goto out;
+ }
+ if( !text_seen )
+ {
+ snprintf( err, es, "program requires a .text section" );
+ goto out;
+ }
+ if( !life_seen ) life_entry = first_text;
+ /* backward-compatible legacy .text image */
+
+ {
+ size_t i;
+ size_t sizes[8] =
+ {
+ data.n, 0, code.n, control.n, input.n, output.n, trace.n, 0
+ };
+ for( i = 0; i < dfix.n; i++ )
+ {
+ __mpu_uint64_t a;
+ int er = eval_u64( dfix.p[i].expr, &eqs, &dlabels, &a, err, es );
+ if( er < 0 )
+ {
+ char cause[256];
+ snprintf( cause, sizeof( cause ), "%s", err );
+ snprintf( err, es, "DATA expression '%s': %s", dfix.p[i].expr, cause );
+ goto out;
+ }
+ if( er > 0 )
+ {
+ snprintf( err, es, "DATA expression out of unsigned 64-bit range: %s", dfix.p[i].expr );
+ goto out;
+ }
+ __cpu_code_put_u64( code.p + dfix.p[i].pos, a );
+ }
+ for( i = 0; i < cfix.n; i++ )
+ {
+ __mpu_uint64_t a;
+ int er = eval_u64( cfix.p[i].expr, &eqs, &clabels, &a, err, es );
+ if( er < 0 )
+ {
+ char cause[256];
+ snprintf( cause, sizeof( cause ), "%s", err );
+ snprintf( err, es, "CODE expression '%s': %s", cfix.p[i].expr, cause );
+ goto out;
+ }
+ if( er > 0 || a >= code.n )
+ {
+ snprintf( err, es, "CODE expression does not address PROGRAM: %s", cfix.p[i].expr );
+ goto out;
+ }
+ __cpu_code_put_u64( code.p + cfix.p[i].pos, a );
+ }
+ for( i = 0; i < pfix.n; i++ )
+ {
+ __mpu_uint64_t ptr;
+ if( resolve_ptr_expression( pfix.p[i].expr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, &ptr, err, es ) ) goto out;
+ __cpu_code_put_u64( code.p + pfix.p[i].pos, ptr );
+ }
+ if( apply_cptr_fixups( &data, &dcptr, &eqs, &clabels, code.n, err, es ) ||
+ apply_cptr_fixups( &control, &ccptr, &eqs, &clabels, code.n, err, es ) ||
+ apply_cptr_fixups( &input, &icptr, &eqs, &clabels, code.n, err, es ) ||
+ apply_cptr_fixups( &output, &ocptr, &eqs, &clabels, code.n, err, es ) ||
+ apply_cptr_fixups( &trace, &tcptr, &eqs, &clabels, code.n, err, es ) ||
+ apply_ptr_data_fixups( &data, &dptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) ||
+ apply_ptr_data_fixups( &control, &ctptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) ||
+ apply_ptr_data_fixups( &input, &iptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) ||
+ apply_ptr_data_fixups( &output, &optr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es ) ||
+ apply_ptr_data_fixups( &trace, &tptr, &eqs, &clabels, &dlabels, &ctlabels, &ilabels, &olabels, &tlabels, sizes, err, es )) goto out;
+ }
+
+ im->code = code.p;
+ im->code_size = code.n;
+ code.p = 0;
+ im->data = data.p;
+ im->data_size = data.n;
+ data.p = 0;
+ im->control = control.p;
+ im->control_size = control.n;
+ control.p = 0;
+ im->input = input.p;
+ im->input_size = input.n;
+ input.p = 0;
+ im->output = output.p;
+ im->output_size = output.n;
+ output.p = 0;
+ im->trace = trace.p;
+ im->trace_size = trace.n;
+ trace.p = 0;
+ im->has_init = init_seen;
+ im->init_entry = start_entry;
+ im->has_life = 1;
+ im->life_entry = life_entry;
+ im->has_fini = fini_seen;
+ im->fini_entry = fini_entry;
+ rc = 0;
+
+out:
+ free( scan );
+ free( copy );
+ free( code.p );
+ free( data.p );
+ free( control.p );
+ free( input.p );
+ free( output.p );
+ free( trace.p );
+ free( clabels.p );
+ free( dlabels.p );
+ free( ctlabels.p );
+ free( ilabels.p );
+ free( olabels.p );
+ free( tlabels.p );
+ free( dfix.p );
+ free( cfix.p );
+ free( pfix.p );
+ free( dcptr.p );
+ free( ccptr.p );
+ free( icptr.p );
+ free( ocptr.p );
+ free( tcptr.p );
+ free( dptr.p );
+ free( ctptr.p );
+ free( iptr.p );
+ free( optr.p );
+ free( tptr.p );
+ free( eqs.p );
+ return( rc );
+}
+
+void
+cpu_asm_image_free( cpu_asm_image *im )
+{
+ size_t i;
+ if( !im ) return;
+ free( im->code );
+ free( im->data );
+ free( im->control );
+ free( im->input );
+ free( im->output );
+ free( im->trace );
+ for( i = 0; i < im->dyn_symbol_count; i++ ) free( im->dyn_symbols[i].name );
+ free( im->dyn_symbols );
+ free( im->dyn_relocs );
+ memset( im, 0, sizeof( *im ) );
+}
+
+/* -------------------------------------------------------------------------
+ * Relocatable assembler path -- MELF logical sections.
+ *
+ * ET_REL keeps .init/.text/.fini distinct and stores explicit RELA-like
+ * semantic records in cpu_asm_object. cpu-object.c maps this logical object
+ * one-to-one onto MELF sections/symbols/RELA records.
+ * ------------------------------------------------------------------------- */
+
+static int
+obj_symbol_defined( const cpu_asm_object *o, const char *name, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < o->symbol_count; i++ )
+ if( !strcmp( o->symbols[i].name, name ) && o->symbols[i].section != CPU_OBJ_SEC_UNDEF )
+ {
+ if( idx ) * idx = i;
+ return( 1 );
+ }
+ return( 0 );
+}
+
+static int
+obj_find_symbol( const cpu_asm_object *o, const char *name, size_t *idx )
+{
+ size_t i;
+ for( i = 0; i < o->symbol_count; i++ ) if( !strcmp( o->symbols[i].name, name ) )
+ {
+ if( idx ) * idx = i;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int obj_add_symbol( cpu_asm_object *o, const char *name, unsigned section, unsigned binding, unsigned type,
+ __mpu_uint64_t value, char *err, size_t es)
+{
+ cpu_asm_symbol *q;
+ size_t i;
+ if( !obj_find_symbol( o, name, &i ) )
+ {
+ cpu_asm_symbol *x = &o->symbols[i];
+ if( x->section != CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF )
+ {
+ snprintf( err, es, "duplicate object symbol: %s", name );
+ return( -1 );
+ }
+ if( x->section == CPU_OBJ_SEC_UNDEF && section != CPU_OBJ_SEC_UNDEF )
+ {
+ x->section = section;
+ x->value = value;
+ x->type = type;
+ }
+ if( binding == CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_WEAK;
+ else if( binding == CPU_OBJ_BIND_GLOBAL && x->binding != CPU_OBJ_BIND_WEAK ) x->binding = CPU_OBJ_BIND_GLOBAL;
+ return( 0 );
+ }
+ q = (cpu_asm_symbol *)realloc( o->symbols, ( o->symbol_count + 1 ) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ o->symbols = q;
+ memset( &o->symbols[o->symbol_count], 0, sizeof( o->symbols[o->symbol_count] ) );
+ o->symbols[o->symbol_count].name = strdup( name );
+ if( !o->symbols[o->symbol_count].name )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ o->symbols[o->symbol_count].section = section;
+ o->symbols[o->symbol_count].binding = binding;
+ o->symbols[o->symbol_count].type = type;
+ o->symbols[o->symbol_count].value = value;
+ o->symbol_count++;
+ return( 0 );
+}
+
+static int obj_add_reloc( cpu_asm_object *o, unsigned section, unsigned type, size_t sym,
+ __mpu_uint64_t off, __mpu_int64_t addend, char *err, size_t es)
+{
+ cpu_asm_reloc *q = (cpu_asm_reloc *)realloc( o->relocs, ( o->reloc_count + 1 ) * sizeof( *q ) );
+ if( !q )
+ {
+ snprintf( err, es, "out of memory" );
+ return( -1 );
+ }
+ o->relocs = q;
+ o->relocs[o->reloc_count].section = section;
+ o->relocs[o->reloc_count].type = type;
+ o->relocs[o->reloc_count].symbol_index = sym;
+ o->relocs[o->reloc_count].offset = off;
+ o->relocs[o->reloc_count].addend = addend;
+ o->reloc_count++;
+ return( 0 );
+}
+
+static int obj_label_lookup( const char *name,
+ const labels_t *init, const labels_t *text, const labels_t *fini,
+ const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace,
+ unsigned *section, __mpu_uint64_t *value)
+{
+ if( init && !findlabel( init, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_INIT;
+ return( 0 );
+ }
+ if( text && !findlabel( text, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_TEXT;
+ return( 0 );
+ }
+ if( fini && !findlabel( fini, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_FINI;
+ return( 0 );
+ }
+ if( data && !findlabel( data, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_DATA;
+ return( 0 );
+ }
+ if( bss && !findlabel( bss, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_BSS;
+ return( 0 );
+ }
+ if( control && !findlabel( control, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_CONTROL;
+ return( 0 );
+ }
+ if( input && !findlabel( input, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_INPUT;
+ return( 0 );
+ }
+ if( output && !findlabel( output, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_OUTPUT;
+ return( 0 );
+ }
+ if( trace && !findlabel( trace, name, value ) )
+ {
+ *section = CPU_OBJ_SEC_TRACE;
+ return( 0 );
+ }
+ return( -1 );
+}
+
+static int obj_name_defined_any( const char *name,
+ const labels_t *init, const labels_t *text, const labels_t *fini,
+ const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace)
+{
+ unsigned s;
+ __mpu_uint64_t v;
+ return( !obj_label_lookup( name, init, text, fini, data, bss, control, input, output, trace, &s, &v ) );
+}
+
+static int reloc_expression_melf( const char *expr, equs_t *eqs, const symdecls_t *decls,
+ const labels_t *init, const labels_t *text, const labels_t *fini,
+ const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace,
+ char sym[ASM_NAME_MAX], int *has_sym, __mpu_int64_t *addend,
+ __mpu_uint64_t *absolute, char *err, size_t es)
+{
+ const char *p = expr;
+ char found[ASM_NAME_MAX] = "";
+ int seen = 0;
+ while( *p )
+ {
+ if( isdigit( (unsigned char)*p ) )
+ {
+ char *end = 0;
+ errno = 0;
+ (void)strtoull( p, &end, 0 );
+ if( end && end > p )
+ {
+ p = end;
+ continue;
+ }
+ }
+ if( symfirst( (unsigned char)*p ) )
+ {
+ char n[ASM_NAME_MAX];
+ size_t k = 0, ei, di;
+ unsigned sec;
+ __mpu_uint64_t val;
+ while( symrest( (unsigned char)*p ) )
+ {
+ if( k + 1 >= sizeof( n ) )
+ {
+ snprintf( err, es, "symbol name too long" );
+ return( -1 );
+ }
+ n[k++] = *p++;
+ }
+ n[k] = 0;
+ if( !findequ( eqs, n, &ei ) ) continue;
+ if( !obj_label_lookup( n, init, text, fini, data, bss, control, input, output, trace, &sec, &val ) || !finddecl( decls, n, &di ) )
+ {
+ if( !seen )
+ {
+ strcpy( found, n );
+ seen = 1;
+ }
+ else
+ if( strcmp( found, n ) )
+ {
+ snprintf( err, es, "relocatable expression contains more than one symbol: %s", expr );
+ return( -1 );
+ } continue;
+ }
+ snprintf( err, es, "unknown symbol in relocatable expression: %s", n );
+ return( -1 );
+ }
+ p++;
+ }
+ if( !seen )
+ {
+ __int128 v;
+ if( eval_expression( expr, eqs, 0, &v, err, es ) ) return( -1 );
+ if( v < 0 || v > ( __int128 ) UINT64_MAX )
+ {
+ snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr );
+ return( -1 );
+ }
+ *has_sym = 0;
+ *absolute = (__mpu_uint64_t)v;
+ return( 0 );
+ }
+ else
+ {
+ labels_t fake =
+ {
+ 0
+ };
+ label_t one;
+ __int128 v0, v1, d;
+ memset( &one, 0, sizeof( one ) );
+ strcpy( one.name, found );
+ fake.p = &one;
+ fake.n = fake.cap = 1;
+ one.addr = 0;
+ if( eval_expression( expr, eqs, &fake, &v0, err, es ) ) return( -1 );
+ one.addr = 1;
+ if( eval_expression( expr, eqs, &fake, &v1, err, es ) ) return( -1 );
+ d = v1 - v0;
+ if( d == 0 )
+ {
+ if( v0 < 0 || v0 > ( __int128 ) UINT64_MAX )
+ {
+ snprintf( err, es, "absolute expression out of unsigned 64-bit range: %s", expr );
+ return( -1 );
+ }
+ *has_sym = 0;
+ *absolute = (__mpu_uint64_t)v0;
+ return( 0 );
+ }
+ if( d != 1 )
+ {
+ snprintf( err, es, "relocatable expression must be SYMBOL + constant: %s", expr );
+ return( -1 );
+ }
+ if( v0 < ( __int128 ) INT64_MIN || v0 > ( __int128 ) INT64_MAX )
+ {
+ snprintf( err, es, "relocation addend out of signed 64-bit range: %s", expr );
+ return( -1 );
+ }
+ strcpy( sym, found );
+ *has_sym = 1;
+ *addend = (__mpu_int64_t)v0;
+ return( 0 );
+ }
+}
+
+static int process_obj_fixups_melf( cpu_asm_object *o, buf_t *src, unsigned srcsec, const fixups_t *fx,
+ unsigned type, equs_t *eqs, const symdecls_t *decls,
+ const labels_t *init, const labels_t *text, const labels_t *fini,
+ const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace, char *err, size_t es)
+{
+ size_t i;
+ for( i = 0; i < fx->n; i++ )
+ {
+ char name[ASM_NAME_MAX];
+ int hs = 0;
+ __mpu_int64_t add = 0;
+ __mpu_uint64_t abs = 0, ptr;
+ size_t si;
+ if( reloc_expression_melf( fx->p[i].expr, eqs, decls, init, text, fini, data, bss, control, input, output, trace, name, &hs, &add, &abs, err, es ) ) return( -1 );
+ if( hs )
+ {
+ if( obj_find_symbol( o, name, &si ) )
+ {
+ snprintf( err, es, "internal assembler symbol lookup failed: %s", name );
+ return( -1 );
+ }
+ if( o->symbols[si].section != CPU_OBJ_SEC_UNDEF )
+ {
+ unsigned ts = o->symbols[si].section;
+ if( type == CPU_RELOC_CODE64 && !cpu_obj_sec_is_program( ts ) )
+ {
+ snprintf( err, es, "CODE64 relocation target is not PROGRAM: %s", name );
+ return( -1 );
+ }
+ if( type == CPU_RELOC_DATA64 && ts != CPU_OBJ_SEC_DATA && ts != CPU_OBJ_SEC_BSS )
+ {
+ snprintf( err, es, "ADDR64 relocation target is not DATA: %s", name );
+ return( -1 );
+ }
+ if( type == CPU_RELOC_CPTR64_DATA && !cpu_obj_sec_is_program( ts ) )
+ {
+ snprintf( err, es, ".cptr relocation target is not PROGRAM: %s", name );
+ return( -1 );
+ }
+ }
+ if( obj_add_reloc( o, srcsec, type, si, fx->p[i].pos, add, err, es ) ) return( -1 );
+ continue;
+ }
+ switch( type )
+ {
+ case CPU_RELOC_CODE64: case CPU_RELOC_DATA64: __cpu_code_put_u64( src->p + fx->p[i].pos, abs );
+ break;
+ case CPU_RELOC_PTR64_CODE:
+ if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) )
+ {
+ snprintf( err, es, "absolute LEA pointer cannot be encoded: %s", fx->p[i].expr );
+ return( -1 );
+ }
+ __cpu_code_put_u64( src->p + fx->p[i].pos, ptr );
+ break;
+ case CPU_RELOC_PTR64_DATA:
+ if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_DATA, abs ) )
+ {
+ snprintf( err, es, "absolute .ptr cannot be encoded: %s", fx->p[i].expr );
+ return( -1 );
+ }
+ __cpu_data_put_u64( src->p + fx->p[i].pos, ptr );
+ break;
+ case CPU_RELOC_CPTR64_DATA:
+ if( __cpu_ptr_make( &ptr, CPU_PTR_SEG_PROGRAM, abs ) )
+ {
+ snprintf( err, es, "absolute .cptr cannot be encoded: %s", fx->p[i].expr );
+ return( -1 );
+ }
+ __cpu_data_put_u64( src->p + fx->p[i].pos, ptr );
+ break;
+ default: snprintf( err, es, "internal unknown relocation type" );
+ return( -1 );
+ }
+ }
+ return( 0 );
+}
+
+static unsigned
+declared_binding( const symdecls_t *d, const char *name )
+{
+ size_t i;
+ if( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) ) return( CPU_OBJ_BIND_GLOBAL );
+ if( !finddecl( d, name, &i ) )
+ {
+ if( d->p[i].is_weak ) return( CPU_OBJ_BIND_WEAK );
+ if( d->p[i].is_global ) return( CPU_OBJ_BIND_GLOBAL );
+ }
+ return( CPU_OBJ_BIND_LOCAL );
+}
+
+static int build_obj_symbols_melf( cpu_asm_object *o, const symdecls_t *decls,
+ const labels_t *init, const labels_t *text, const labels_t *fini,
+ const labels_t *data, const labels_t *bss, const labels_t *control, const labels_t *input,
+ const labels_t *output, const labels_t *trace, char *err, size_t es)
+{
+ const labels_t *ls[9] =
+ {
+ init, text, fini, data, bss, control, input, output, trace
+ };
+ const unsigned sec[9] =
+ {
+ CPU_OBJ_SEC_INIT, CPU_OBJ_SEC_TEXT, CPU_OBJ_SEC_FINI, CPU_OBJ_SEC_DATA, CPU_OBJ_SEC_BSS, CPU_OBJ_SEC_CONTROL, CPU_OBJ_SEC_INPUT, CPU_OBJ_SEC_OUTPUT, CPU_OBJ_SEC_TRACE
+ };
+ size_t j, i;
+ for( j = 0; j < 9; j++ ) for( i = 0; i < ls[j]->n; i++ )
+ {
+ unsigned type = cpu_obj_sec_is_program( sec[j] ) ? CPU_OBJ_TYPE_FUNC : CPU_OBJ_TYPE_OBJECT;
+ if( obj_add_symbol( o, ls[j]->p[i].name, sec[j], declared_binding( decls, ls[j]->p[i].name ), type, ls[j]->p[i].addr, err, es ) ) return( -1 );
+ }
+ for( i = 0; i < decls->n; i++ ) if( !obj_symbol_defined( o, decls->p[i].name, 0 ) )
+ {
+ const char *name = decls->p[i].name;
+ unsigned b = decls->p[i].is_weak ? CPU_OBJ_BIND_WEAK : CPU_OBJ_BIND_GLOBAL;
+ unsigned type = ( !strcmp( name, "_start" ) || !strcmp( name, "_life" ) || !strcmp( name, "_fini" ) )
+ ?CPU_OBJ_TYPE_FUNC:CPU_OBJ_TYPE_NOTYPE;
+ if( obj_add_symbol( o, name, CPU_OBJ_SEC_UNDEF, b, type, 0, err, es ) ) return( -1 );
+ }
+ return( 0 );
+}
+
+int
+cpu_assemble_object( const char *src, cpu_asm_object *o, char *err, size_t es )
+{
+ char *copy = 0, *scan = 0, *save = 0, *line;
+ buf_t init =
+ {
+ 0
+ }
+ , text =
+ {
+ 0
+ }
+ , fini =
+ {
+ 0
+ }
+ , data =
+ {
+ 0
+ }
+ , control =
+ {
+ 0
+ }
+ , input =
+ {
+ 0
+ }
+ , output =
+ {
+ 0
+ }
+ , trace =
+ {
+ 0
+ };
+ size_t bss_size = 0;
+ labels_t ilabs =
+ {
+ 0
+ }
+ , tlabs =
+ {
+ 0
+ }
+ , flabs =
+ {
+ 0
+ }
+ , dlabs =
+ {
+ 0
+ }
+ , blabs =
+ {
+ 0
+ }
+ , ctlabs =
+ {
+ 0
+ }
+ , inlabs =
+ {
+ 0
+ }
+ , olabs =
+ {
+ 0
+ }
+ , trlabs =
+ {
+ 0
+ };
+ fixups_t idfix =
+ {
+ 0
+ }
+ , icfix =
+ {
+ 0
+ }
+ , ipfix =
+ {
+ 0
+ }
+ , tdfix =
+ {
+ 0
+ }
+ , tcfix =
+ {
+ 0
+ }
+ , tpfix =
+ {
+ 0
+ }
+ , fdfix =
+ {
+ 0
+ }
+ , fcfix =
+ {
+ 0
+ }
+ , fpfix =
+ {
+ 0
+ };
+ fixups_t dcptr =
+ {
+ 0
+ }
+ , ccptr =
+ {
+ 0
+ }
+ , incptr =
+ {
+ 0
+ }
+ , ocptr =
+ {
+ 0
+ }
+ , trcptr =
+ {
+ 0
+ };
+ fixups_t dptr =
+ {
+ 0
+ }
+ , ctptr =
+ {
+ 0
+ }
+ , inptr =
+ {
+ 0
+ }
+ , optr =
+ {
+ 0
+ }
+ , trptr =
+ {
+ 0
+ };
+ __mpu_uint64_t dalign = 1, balign = 1, ctalign = 1, inalign = 1, oalign = 1, tralign = 1;
+ equs_t eqs =
+ {
+ 0
+ };
+ symdecls_t decls =
+ {
+ 0
+ };
+ int section = ASM_SEC_NONE, line_no = 0, rc = -1;
+ int init_seen = 0, text_seen = 0, fini_seen = 0, bss_seen = 0, pseudo_seen = 0;
+
+ if( !src || !o ) return( -1 );
+ memset( o, 0, sizeof( *o ) );
+ if( !mpu_utf8valid( (const __mpu_char8_t *)src ) )
+ {
+ snprintf( err, es, "assembler source is not valid UTF-8" );
+ return( -1 );
+ }
+
+ scan = strdup( src );
+ if( !scan ) return( -1 );
+ for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) )
+ {
+ int er;
+ line_no++;
+ strip_comment( line );
+ if( !*trim( line ) ) continue;
+ er = collect_equ_line( line, &eqs, line_no, err, es );
+ if( er < 0 ) goto out;
+ }
+ free( scan );
+ scan = 0;
+ save = 0;
+ {
+ size_t i;
+ __int128 v;
+ for( i = 0; i < eqs.n; i++ ) if( equ_value( &eqs, i, &v, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", eqs.p[i].line, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ }
+ }
+
+ scan = strdup( src );
+ if( !scan ) goto out;
+ line_no = 0;
+ for( line = strtok_r( scan, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) )
+ {
+ int er;
+ line_no++;
+ strip_comment( line );
+ if( !*trim( line ) ) continue;
+ er = collect_decl_line( line, &decls, line_no, err, es );
+ if( er < 0 ) goto out;
+ }
+ free( scan );
+ scan = 0;
+ save = 0;
+ {
+ size_t i, j;
+ for( i = 0; i < eqs.n; i++ ) for( j = 0; j < decls.n; j++ ) if( !strcmp( eqs.p[i].name, decls.p[j].name ) )
+ {
+ snprintf( err, es, "symbol declared global/extern/weak conflicts with .equ: %s", eqs.p[i].name );
+ goto out;
+ }
+ }
+
+ copy = strdup( src );
+ if( !copy ) goto out;
+ line_no = 0;
+ for( line = strtok_r( copy, "\n", &save ); line; line = strtok_r( NULL, "\n", &save ) )
+ {
+ char *p, *colon, *mn, *rest, *q;
+ buf_t *cb = 0, *db = 0;
+ labels_t *labs = 0;
+ fixups_t *df = 0, *cf = 0, *pf = 0, *cpfx = 0, *gptr = 0;
+ __mpu_uint64_t *salign = 0;
+ int in_bss = 0;
+ line_no++;
+ strip_comment( line );
+ p = trim( line );
+ if( !*p ) continue;
+ q = p;
+ while( *q && !isspace( (unsigned char)*q ) ) q++;
+ if( ( (size_t)( q - p ) == 4 && !strncasecmp( p, ".equ", 4 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".global", 7 ) ) || ( (size_t)( q - p ) == 6 && !strncasecmp( p, ".globl", 6 ) ) || ( (size_t)( q - p ) == 7 && !strncasecmp( p, ".extern", 7 ) ) || ( (size_t)( q - p ) == 5 && !strncasecmp( p, ".weak", 5 ) ) ) continue;
+
+ if( streqi( p, ".init" ) )
+ {
+ init_seen = 1;
+ section = ASM_SEC_INIT;
+ continue;
+ }
+ if( streqi( p, ".text" ) )
+ {
+ text_seen = 1;
+ section = ASM_SEC_TEXT;
+ continue;
+ }
+ if( streqi( p, ".fini" ) )
+ {
+ fini_seen = 1;
+ section = ASM_SEC_FINI;
+ continue;
+ }
+ if( streqi( p, ".data" ) )
+ {
+ section = ASM_SEC_DATA;
+ continue;
+ }
+ if( streqi( p, ".bss" ) )
+ {
+ bss_seen = 1;
+ section = ASM_SEC_BSS;
+ continue;
+ }
+ if( streqi( p, ".control" ) )
+ {
+ section = ASM_SEC_CONTROL;
+ continue;
+ }
+ if( streqi( p, ".input" ) )
+ {
+ section = ASM_SEC_INPUT;
+ continue;
+ }
+ if( streqi( p, ".output" ) )
+ {
+ section = ASM_SEC_OUTPUT;
+ continue;
+ }
+ if( streqi( p, ".trace" ) )
+ {
+ section = ASM_SEC_TRACE;
+ continue;
+ }
+
+ switch( section )
+ {
+ case ASM_SEC_INIT: cb = &init;
+ labs = &ilabs;
+ df = &idfix;
+ cf = &icfix;
+ pf = &ipfix;
+ break;
+ case ASM_SEC_TEXT: cb = &text;
+ labs = &tlabs;
+ df = &tdfix;
+ cf = &tcfix;
+ pf = &tpfix;
+ break;
+ case ASM_SEC_FINI: cb = &fini;
+ labs = &flabs;
+ df = &fdfix;
+ cf = &fcfix;
+ pf = &fpfix;
+ break;
+ case ASM_SEC_DATA: db = &data;
+ labs = &dlabs;
+ cpfx = &dcptr;
+ gptr = &dptr;
+ salign = &dalign;
+ break;
+ case ASM_SEC_BSS: labs = &blabs;
+ salign = &balign;
+ in_bss = 1;
+ break;
+ case ASM_SEC_CONTROL: db = &control;
+ labs = &ctlabs;
+ cpfx = &ccptr;
+ gptr = &ctptr;
+ salign = &ctalign;
+ break;
+ case ASM_SEC_INPUT: db = &input;
+ labs = &inlabs;
+ cpfx = &incptr;
+ gptr = &inptr;
+ salign = &inalign;
+ break;
+ case ASM_SEC_OUTPUT: db = &output;
+ labs = &olabs;
+ cpfx = &ocptr;
+ gptr = &optr;
+ salign = &oalign;
+ break;
+ case ASM_SEC_TRACE: db = &trace;
+ labs = &trlabs;
+ cpfx = &trcptr;
+ gptr = &trptr;
+ salign = &tralign;
+ break;
+ default:break;
+ }
+
+ colon = label_colon( p );
+ if( colon )
+ {
+ size_t ei;
+ char *name;
+ __mpu_uint64_t here;
+ *colon = 0;
+ name = trim( p );
+ if( !valid_symbol_name( name ) )
+ {
+ snprintf( err, es, "line %d: bad label name", line_no );
+ goto out;
+ }
+ if( !findequ( &eqs, name, &ei ) )
+ {
+ snprintf( err, es, "line %d: label conflicts with .equ symbol: %s", line_no, name );
+ goto out;
+ }
+ if( !strcmp( name, "_start_life" ) )
+ {
+ if( section != ASM_SEC_TEXT )
+ {
+ snprintf( err, es, "line %d: _start_life is valid only in .text", line_no );
+ goto out;
+ }
+ if( pseudo_seen || obj_name_defined_any( "_start", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) || obj_name_defined_any( "_life", &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) )
+ {
+ snprintf( err, es, "line %d: _start_life conflicts with an existing _start/_life definition", line_no );
+ goto out;
+ }
+ here = (__mpu_uint64_t)text.n;
+ if( addlabel( &tlabs, "_start", here ) || addlabel( &tlabs, "_life", here ) )
+ {
+ snprintf( err, es, "line %d: cannot define _start_life", line_no );
+ goto out;
+ }
+ pseudo_seen = 1;
+ }
+ else
+ {
+ if( obj_name_defined_any( name, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs ) )
+ {
+ snprintf( err, es, "line %d: duplicate label: %s", line_no, name );
+ goto out;
+ }
+ if( !labs )
+ {
+ snprintf( err, es, "line %d: label outside a valid section", line_no );
+ goto out;
+ }
+ here = cb ? (__mpu_uint64_t)cb->n : in_bss ? (__mpu_uint64_t)bss_size : (__mpu_uint64_t)db->n;
+ if( addlabel( labs, name, here ) )
+ {
+ snprintf( err, es, "line %d: bad/duplicate label", line_no );
+ goto out;
+ }
+ if( !strcmp( name, "_start" ) && section != ASM_SEC_INIT && section != ASM_SEC_TEXT )
+ {
+ snprintf( err, es, "line %d: _start must be in .init or .text", line_no );
+ goto out;
+ }
+ if( !strcmp( name, "_life" ) && section != ASM_SEC_TEXT )
+ {
+ snprintf( err, es, "line %d: _life must be in .text", line_no );
+ goto out;
+ }
+ if( !strcmp( name, "_fini" ) && section != ASM_SEC_FINI )
+ {
+ snprintf( err, es, "line %d: _fini must be in .fini", line_no );
+ goto out;
+ }
+ }
+ p = trim( colon + 1 );
+ if( !*p ) continue;
+ }
+
+ if( in_bss )
+ {
+ if( bssdir( &bss_size, p, salign, &eqs, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ } continue;
+ }
+ if( db )
+ {
+ if( datadir( db, p, cpfx, gptr, salign, &eqs, err, es ) )
+ {
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+ } continue;
+ }
+ if( !cb )
+ {
+ snprintf( err, es, "line %d: select a code or data section", line_no );
+ goto out;
+ }
+ mn = p;
+ while( *p && !isspace( (unsigned char)*p ) ) p++;
+ if( *p )
+ {
+ *p++ = 0;
+ rest = trim( p );
+ }
+ else rest = (char *)"";
+ if( streqi( mn, "LDI" ) )
+ {
+ if( !*rest || emit_ldi( cb, &eqs, rest, err, es ) ) goto badline_obj;
+ }
+ else if( emit_instruction( cb, df, cf, pf, &eqs, mn, rest, err, es ) ) goto badline_obj;
+ continue;
+badline_obj:
+{
+ char t[256];
+ snprintf( t, sizeof( t ), "line %d: %s", line_no, err );
+ snprintf( err, es, "%s", t );
+ goto out;
+}
+ }
+
+ if( init_seen && obj_name_defined_any( "_start", 0, &tlabs, 0, 0, 0, 0, 0, 0, 0 ) )
+ {
+ snprintf( err, es, "_start must be in .init when .init exists" );
+ goto out;
+ }
+ if( init_seen && pseudo_seen )
+ {
+ snprintf( err, es, "_start_life is invalid when .init exists" );
+ goto out;
+ }
+
+ if( build_obj_symbols_melf( o, &decls, &ilabs, &tlabs, &flabs, &dlabs, &blabs, &ctlabs, &inlabs, &olabs, &trlabs, err, es ) ) goto out;
+#define PF(SRC,SEC,FX,TYPE) process_obj_fixups_melf(o,&(SRC),(SEC),&(FX),(TYPE),&eqs,&decls,&ilabs,&tlabs,&flabs,&dlabs,&blabs,&ctlabs,&inlabs,&olabs,&trlabs,err,es)
+ if( PF( init, CPU_OBJ_SEC_INIT, idfix, CPU_RELOC_DATA64 ) || PF( init, CPU_OBJ_SEC_INIT, icfix, CPU_RELOC_CODE64 ) || PF( init, CPU_OBJ_SEC_INIT, ipfix, CPU_RELOC_PTR64_CODE ) ||
+ PF( text, CPU_OBJ_SEC_TEXT, tdfix, CPU_RELOC_DATA64 ) || PF( text, CPU_OBJ_SEC_TEXT, tcfix, CPU_RELOC_CODE64 ) || PF( text, CPU_OBJ_SEC_TEXT, tpfix, CPU_RELOC_PTR64_CODE ) ||
+ PF( fini, CPU_OBJ_SEC_FINI, fdfix, CPU_RELOC_DATA64 ) || PF( fini, CPU_OBJ_SEC_FINI, fcfix, CPU_RELOC_CODE64 ) || PF( fini, CPU_OBJ_SEC_FINI, fpfix, CPU_RELOC_PTR64_CODE ) ||
+ PF( data, CPU_OBJ_SEC_DATA, dcptr, CPU_RELOC_CPTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ccptr, CPU_RELOC_CPTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, incptr, CPU_RELOC_CPTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, ocptr, CPU_RELOC_CPTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trcptr, CPU_RELOC_CPTR64_DATA ) ||
+ PF( data, CPU_OBJ_SEC_DATA, dptr, CPU_RELOC_PTR64_DATA ) || PF( control, CPU_OBJ_SEC_CONTROL, ctptr, CPU_RELOC_PTR64_DATA ) || PF( input, CPU_OBJ_SEC_INPUT, inptr, CPU_RELOC_PTR64_DATA ) || PF( output, CPU_OBJ_SEC_OUTPUT, optr, CPU_RELOC_PTR64_DATA ) || PF( trace, CPU_OBJ_SEC_TRACE, trptr, CPU_RELOC_PTR64_DATA )) goto out;
+#undef PF
+
+ o->init = init.p;
+ o->init_size = init.n;
+ o->init_align = 1;
+ o->has_init = init_seen;
+ init.p = 0;
+ o->text = text.p;
+ o->text_size = text.n;
+ o->text_align = 1;
+ o->has_text = text_seen;
+ text.p = 0;
+ o->fini = fini.p;
+ o->fini_size = fini.n;
+ o->fini_align = 1;
+ o->has_fini = fini_seen;
+ fini.p = 0;
+ o->data = data.p;
+ o->data_size = data.n;
+ o->data_align = dalign;
+ o->has_data = ( data.n != 0 );
+ data.p = 0;
+ o->bss_size = bss_size;
+ o->bss_align = balign;
+ o->has_bss = bss_seen;
+ o->control = control.p;
+ o->control_size = control.n;
+ o->control_align = ctalign;
+ o->has_control = ( control.n != 0 );
+ control.p = 0;
+ o->input = input.p;
+ o->input_size = input.n;
+ o->input_align = inalign;
+ o->has_input = ( input.n != 0 );
+ input.p = 0;
+ o->output = output.p;
+ o->output_size = output.n;
+ o->output_align = oalign;
+ o->has_output = ( output.n != 0 );
+ output.p = 0;
+ o->trace = trace.p;
+ o->trace_size = trace.n;
+ o->trace_align = tralign;
+ o->has_trace = ( trace.n != 0 );
+ trace.p = 0;
+ o->code_size = o->init_size + o->text_size + o->fini_size;
+ o->code_align = 1;
+ if( o->code_size )
+ {
+ o->code = (__mpu_UNIT_t *)malloc( o->code_size );
+ if( !o->code )
+ {
+ snprintf( err, es, "out of memory" );
+ goto out;
+ }
+ memcpy( o->code, o->init, o->init_size );
+ memcpy( o->code + o->init_size, o->text, o->text_size );
+ memcpy( o->code + o->init_size + o->text_size, o->fini, o->fini_size );
+ }
+ rc = 0;
+out:
+ free( scan );
+ free( copy );
+ free( init.p );
+ free( text.p );
+ free( fini.p );
+ free( data.p );
+ free( control.p );
+ free( input.p );
+ free( output.p );
+ free( trace.p );
+ free( ilabs.p );
+ free( tlabs.p );
+ free( flabs.p );
+ free( dlabs.p );
+ free( blabs.p );
+ free( ctlabs.p );
+ free( inlabs.p );
+ free( olabs.p );
+ free( trlabs.p );
+ free( idfix.p );
+ free( icfix.p );
+ free( ipfix.p );
+ free( tdfix.p );
+ free( tcfix.p );
+ free( tpfix.p );
+ free( fdfix.p );
+ free( fcfix.p );
+ free( fpfix.p );
+ free( dcptr.p );
+ free( ccptr.p );
+ free( incptr.p );
+ free( ocptr.p );
+ free( trcptr.p );
+ free( dptr.p );
+ free( ctptr.p );
+ free( inptr.p );
+ free( optr.p );
+ free( trptr.p );
+ free( eqs.p );
+ free( decls.p );
+ if( rc ) cpu_asm_object_free( o );
+ return( rc );
+}
+
+void
+cpu_asm_object_free( cpu_asm_object *o )
+{
+ size_t i;
+ if( !o ) return;
+ free( o->init );
+ free( o->text );
+ free( o->fini );
+ free( o->data );
+ free( o->control );
+ free( o->input );
+ free( o->output );
+ free( o->trace );
+ free( o->code );
+ for( i = 0; i < o->symbol_count; i++ ) free( o->symbols[i].name );
+ free( o->symbols );
+ free( o->relocs );
+ memset( o, 0, sizeof( *o ) );
+}