From d4e57278c6f9ce681dc8c263616157226c76cd2f Mon Sep 17 00:00:00 2001 From: Kugel Fuhr <98353208+kugelfuhr@users.noreply.github.com> Date: Mon, 30 Jun 2025 21:37:43 +0200 Subject: [PATCH] Add a new .cap pseudo function to the assembler that allows to check for certain capabilities of the CPU or target system. --- doc/ca65.sgml | 54 +++++++++++ src/ca65/expr.c | 76 ++++++++++++++-- src/ca65/pseudo.c | 4 +- src/ca65/scanner.c | 2 + src/ca65/token.h | 1 + src/common/capability.c | 90 +++++++++++++++++++ src/common/capability.h | 78 ++++++++++++++++ src/common/cpu.c | 82 +++++++++++++++++ src/common/cpu.h | 8 ++ test/asm/listing/110-capabilities.s | 39 ++++++++ test/asm/listing/control/110-capabilities.err | 0 .../asm/listing/ref/110-capabilities.err2-ref | 6 ++ 12 files changed, 434 insertions(+), 6 deletions(-) create mode 100644 src/common/capability.c create mode 100644 src/common/capability.h create mode 100644 test/asm/listing/110-capabilities.s create mode 100644 test/asm/listing/control/110-capabilities.err create mode 100644 test/asm/listing/ref/110-capabilities.err2-ref diff --git a/doc/ca65.sgml b/doc/ca65.sgml index b7e8539af..552309ff9 100644 --- a/doc/ca65.sgml +++ b/doc/ca65.sgml @@ -1423,6 +1423,10 @@ writable. + See also: + + + .ISIZE Reading this pseudo variable will return the current size of the Index @@ -1594,6 +1598,56 @@ either a string or an expression value. +.CAP, .CAPABILITY + + Builtin function. The function allows to check for capabilities of the + currently selected CPU or target system. It must be called with a comma + separated list of identifiers and returns non zero if all of the given + capabilities are available. Otherwise it returns zero. + + Existing capabilities are: + + + + CPU_HAS_BRA8 + Checks for the availability of a short (8 bit) branch. + + CPU_HAS_INA + Checks for the availability of accu inc/dec instructions. + + CPU_HAS_PUSHXY + Checks for the capability to push and pop the X and Y registers. + + CPU_HAS_ZPIND + Checks for the availability of the "zeropage indirect" addressing mode. + + CPU_HAS_STZ + Checks for the availability of the "store zero" instruction. + + + + Case is ignored when checking the identifiers. The + .if .cap(CPU_HAS_BRA, CPU_HAS_PUSHXY) + phx + bra L1 + .else + txa + pha + jmp L1 + .endif + + + See also: + + + .CONCAT Builtin string function. The function allows to concatenate a list of string diff --git a/src/ca65/expr.c b/src/ca65/expr.c index 5dcf5ca71..42b1a369b 100644 --- a/src/ca65/expr.c +++ b/src/ca65/expr.c @@ -37,6 +37,7 @@ #include /* common */ +#include "capability.h" #include "check.h" #include "cpu.h" #include "exprdefs.h" @@ -405,6 +406,66 @@ static ExprNode* FuncBlank (void) +static ExprNode* FuncCapability (void) +/* Handle the .CAPABILITY builtin function */ +{ + int Result = 1; + + /* What follows is a comma separated list of identifiers. An empty list is + ** not allowed. + */ + while (1) { + + const char* Name; + capability_t Cap; + + /* We must have an identifier */ + if (CurTok.Tok != TOK_IDENT) { + Error ("Arguments to .CAPABILITY must be identifiers"); + /* Skip tokens until closing paren or end of line */ + while (CurTok.Tok != TOK_RPAREN && !TokIsSep (CurTok.Tok)) { + NextTok (); + } + return GenLiteral0 (); + } + + /* Search for the capability that matches this identifier. Ignore case + ** on the specified capabilities. + */ + UpcaseSVal (); + SB_Terminate (&CurTok.SVal); + Name = SB_GetConstBuf (&CurTok.SVal); + Cap = FindCapability (Name); + + /* Check if the capability is supported */ + if (Cap == CAP_INVALID) { + Error ("Not a valid capability name: %s", Name); + Result = 0; + } else { + /* The pseudo function result is the logical AND of all capabilities + ** given. + */ + if (!CPUHasCap (Cap)) { + Result = 0; + } + } + + /* Skip the capability name */ + NextTok (); + + /* Handle end of list or next capability */ + if (CurTok.Tok != TOK_COMMA) { + break; + } + NextTok (); + } + + /* Done */ + return GenLiteralExpr (Result); +} + + + static ExprNode* FuncConst (void) /* Handle the .CONST builtin function */ { @@ -484,9 +545,10 @@ static ExprNode* FuncIsMnemonic (void) if (FindMacro (&CurTok.SVal) == 0) { Instr = FindInstruction (&CurTok.SVal); } - } - else { - /* Macros and symbols may NOT use the names of instructions, so just check for the instruction */ + } else { + /* Macros and symbols may NOT use the names of instructions, so + ** just check for the instruction. + */ Instr = FindInstruction (&CurTok.SVal); } } @@ -532,7 +594,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) token_t Term = GetTokListTerm (TOK_COMMA); while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -570,7 +632,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) Node = Root; while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -1129,6 +1191,10 @@ static ExprNode* Factor (void) N = Function (FuncBlank); break; + case TOK_CAP: + N = Function (FuncCapability); + break; + case TOK_CONST: N = Function (FuncConst); break; diff --git a/src/ca65/pseudo.c b/src/ca65/pseudo.c index e6e10df2c..301129583 100644 --- a/src/ca65/pseudo.c +++ b/src/ca65/pseudo.c @@ -2114,7 +2114,8 @@ struct CtrlDesc { }; /* NOTE: .AND, .BITAND, .BITNOT, .BITOR, .BITXOR, .MOD, .NOT, .OR, .SHL, .SHR - and .XOR do NOT go into this table */ +** and .XOR do NOT go into this table. +*/ #define PSEUDO_COUNT (sizeof (CtrlCmdTab) / sizeof (CtrlCmdTab [0])) static CtrlDesc CtrlCmdTab [] = { { ccNone, DoA16 }, /* .A16 */ @@ -2132,6 +2133,7 @@ static CtrlDesc CtrlCmdTab [] = { { ccNone, DoUnexpected }, /* .BLANK */ { ccNone, DoBss }, /* .BSS */ { ccNone, DoByte }, /* .BYT, .BYTE */ + { ccNone, DoUnexpected }, /* .CAP */ { ccNone, DoCase }, /* .CASE */ { ccNone, DoCharMap }, /* .CHARMAP */ { ccNone, DoCode }, /* .CODE */ diff --git a/src/ca65/scanner.c b/src/ca65/scanner.c index f3892d32e..a2a72a149 100644 --- a/src/ca65/scanner.c +++ b/src/ca65/scanner.c @@ -158,6 +158,8 @@ struct DotKeyword { { ".BSS", TOK_BSS }, { ".BYT", TOK_BYTE }, { ".BYTE", TOK_BYTE }, + { ".CAP", TOK_CAP }, + { ".CAPABILITY", TOK_CAP }, { ".CASE", TOK_CASE }, { ".CHARMAP", TOK_CHARMAP }, { ".CODE", TOK_CODE }, diff --git a/src/ca65/token.h b/src/ca65/token.h index e2b223880..b40534d79 100644 --- a/src/ca65/token.h +++ b/src/ca65/token.h @@ -137,6 +137,7 @@ typedef enum token_t { TOK_BLANK, TOK_BSS, TOK_BYTE, + TOK_CAP, TOK_CASE, TOK_CHARMAP, TOK_CODE, diff --git a/src/common/capability.c b/src/common/capability.c new file mode 100644 index 000000000..f66205e7c --- /dev/null +++ b/src/common/capability.c @@ -0,0 +1,90 @@ +/*****************************************************************************/ +/* */ +/* capability.c */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#include + +/* ca65 */ +#include "capability.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* List of dot keywords with the corresponding ids. */ +/* CAUTION: table must be sorted for bsearch. */ +struct Capability { + const char* Key; + capability_t Cap; +} Capabilities [] = { +/* BEGIN SORTED.SH */ + { "CPU_HAS_BRA8", CAP_CPU_HAS_BRA8 }, + { "CPU_HAS_INA", CAP_CPU_HAS_INA }, + { "CPU_HAS_PUSHXY", CAP_CPU_HAS_PUSHXY }, + { "CPU_HAS_STZ", CAP_CPU_HAS_STZ }, + { "CPU_HAS_ZPIND", CAP_CPU_HAS_ZPIND }, +/* END SORTED.SH */ +}; +#define CAP_TABLE_SIZE (sizeof (Capabilities) / sizeof (Capabilities [0])) + + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static int CmpCapability (const void* K1, const void* K2) +/* Compare function for the capability search */ +{ + return strcmp (((struct Capability*)K1)->Key, ((struct Capability*)K2)->Key); +} + + + +capability_t FindCapability (const char* Name) +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ +{ + const struct Capability K = { Name, 0 }; + const struct Capability* C = bsearch (&K, Capabilities, CAP_TABLE_SIZE, + sizeof (Capabilities [0]), + CmpCapability); + return (C == 0)? CAP_INVALID : C->Cap; +} diff --git a/src/common/capability.h b/src/common/capability.h new file mode 100644 index 000000000..011e2164f --- /dev/null +++ b/src/common/capability.h @@ -0,0 +1,78 @@ +/*****************************************************************************/ +/* */ +/* capability.h */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#ifndef CAPABILITY_H +#define CAPABILITY_H + + + +/* common */ +#include "strbuf.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Numeric codes for capabilities */ +enum capability_t { + CAP_INVALID = -1, + CAP_CPU_HAS_BRA8 = 0, /* CPU has a BRA 8-bit instruction */ + CAP_CPU_HAS_INA = 1, /* CPU has DEA/INA */ + CAP_CPU_HAS_PUSHXY = 2, /* CPU has PHX/PHY/PLX/PLY */ + CAP_CPU_HAS_ZPIND = 3, /* CPU has "(zp)" mode (no offset) */ + CAP_CPU_HAS_STZ = 4, /* CPU has "store zero" (!) instruction */ +}; +typedef enum capability_t capability_t; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +capability_t FindCapability (const char* Name); +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ + + + +/* End of capability.h */ + +#endif diff --git a/src/common/cpu.c b/src/common/cpu.c index 252283211..cdc8e52cc 100644 --- a/src/common/cpu.c +++ b/src/common/cpu.c @@ -33,6 +33,8 @@ +#include + /* common */ #include "addrsize.h" #include "check.h" @@ -90,6 +92,77 @@ const unsigned CPUIsets[CPU_COUNT] = { CPU_ISET_65CE02 | CPU_ISET_6502 | CPU_ISET_65C02, }; +/* Defines for capabilities. Currently the entries are uint32_ts but the table +** is deliberately hidden from the outside so it can be extended to 64 bit or +** even more. +*/ +#define CAP_NONE UINT32_C (0) +#define CAP_6502 UINT32_C (0) +#define CAP_6502X UINT32_C (0) +#define CAP_6502DTV UINT32_C (0) +#define CAP_65SC02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65816 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_SWEET16 UINT32_C (0) +#define CAP_HUC6280 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_M740 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA)) +#define CAP_4510 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_45GS02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_W65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_65CE02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) + +/* Table containing one capability entry per CPU */ +static const uint64_t CPUCaps[CPU_COUNT] = { + CAP_NONE, /* CPU_NONE */ + CAP_6502, /* CPU_6502 */ + CAP_6502X, /* CPU_6502X */ + CAP_6502DTV, /* CPU_6502DTV */ + CAP_65SC02, /* CPU_65SC02 */ + CAP_65C02, /* CPU_65C02 */ + CAP_65816, /* CPU_65816 */ + CAP_SWEET16, /* CPU_SWEET16 */ + CAP_HUC6280, /* CPU_HUC6280 */ + CAP_M740, /* CPU_M740 */ + CAP_4510, /* CPU_4510 */ + CAP_45GS02, /* CPU_45GS02 */ + CAP_W65C02, /* CPU_W65C02 */ + CAP_65CE02, /* CPU_65CE02 */ +}; + /*****************************************************************************/ @@ -148,3 +221,12 @@ cpu_t FindCPU (const char* Name) /* Not found */ return CPU_UNKNOWN; } + + + +int CPUHasCap (capability_t Cap) +/* Check if the current CPU has the given capability */ +{ + PRECONDITION (CPU >= 0 && CPU < CPU_COUNT); + return (CPUCaps[CPU] & (UINT32_C (1) << Cap)) != 0; +} diff --git a/src/common/cpu.h b/src/common/cpu.h index 4202ed573..0a7c853ac 100644 --- a/src/common/cpu.h +++ b/src/common/cpu.h @@ -38,6 +38,11 @@ +/* common */ +#include "capability.h" + + + /*****************************************************************************/ /* Data */ /*****************************************************************************/ @@ -107,6 +112,9 @@ cpu_t FindCPU (const char* Name); ** the given name is no valid target. */ +int CPUHasCap (capability_t Cap); +/* Check if the current CPU has the given capability */ + /* End of cpu.h */ diff --git a/test/asm/listing/110-capabilities.s b/test/asm/listing/110-capabilities.s new file mode 100644 index 000000000..e3e306ca7 --- /dev/null +++ b/test/asm/listing/110-capabilities.s @@ -0,0 +1,39 @@ + +; Error: Arguments to .CAPABILITY must be identifiers +.if .cap() +.endif + +; Error: Arguments to .CAPABILITY must be identifiers +; Error: ')' expected +.if .cap( +.endif + +; Error: Not a valid capability name: CPU_HAS_BR +.if .cap(cpu_has_br) +.endif + +; Error: ')' expected +; Error: Unexpected trailing garbage characters +.if .cap(cpu_has_bra8 cpu_has_bra8) +.endif + +; Ok +.if .cap(cpu_has_bra8, CPU_HAS_PUSHXY, CPU_HAS_STZ, CPU_HAS_INA) +.endif + +.setcpu "65SC02" +.if !.cap(cpu_has_bra8) +.error "Assembler says 65SC02 has no 8 bit bra" +.endif +.if !.cap(cpu_has_PUSHXY) +.error "Assembler says 65SC02 has no phx" +.endif +.if !.cap(cpu_has_STZ) +.error "Assembler says 65SC02 has no stz" +.endif +.if !.cap(cpu_has_INA) +.error "Assembler says 65SC02 has no ina" +.endif + + + diff --git a/test/asm/listing/control/110-capabilities.err b/test/asm/listing/control/110-capabilities.err new file mode 100644 index 000000000..e69de29bb diff --git a/test/asm/listing/ref/110-capabilities.err2-ref b/test/asm/listing/ref/110-capabilities.err2-ref new file mode 100644 index 000000000..e00e60e17 --- /dev/null +++ b/test/asm/listing/ref/110-capabilities.err2-ref @@ -0,0 +1,6 @@ +110-capabilities.s:3: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: ')' expected +110-capabilities.s:12: Error: Not a valid capability name: CPU_HAS_BR +110-capabilities.s:17: Error: ')' expected +110-capabilities.s:17: Error: Unexpected trailing garbage characters
Reading this pseudo variable will return the current size of the Index @@ -1594,6 +1598,56 @@ either a string or an expression value. +.CAP, .CAPABILITY + + Builtin function. The function allows to check for capabilities of the + currently selected CPU or target system. It must be called with a comma + separated list of identifiers and returns non zero if all of the given + capabilities are available. Otherwise it returns zero. + + Existing capabilities are: + + + + CPU_HAS_BRA8 + Checks for the availability of a short (8 bit) branch. + + CPU_HAS_INA + Checks for the availability of accu inc/dec instructions. + + CPU_HAS_PUSHXY + Checks for the capability to push and pop the X and Y registers. + + CPU_HAS_ZPIND + Checks for the availability of the "zeropage indirect" addressing mode. + + CPU_HAS_STZ + Checks for the availability of the "store zero" instruction. + + + + Case is ignored when checking the identifiers. The + .if .cap(CPU_HAS_BRA, CPU_HAS_PUSHXY) + phx + bra L1 + .else + txa + pha + jmp L1 + .endif + + + See also: + + + .CONCAT Builtin string function. The function allows to concatenate a list of string diff --git a/src/ca65/expr.c b/src/ca65/expr.c index 5dcf5ca71..42b1a369b 100644 --- a/src/ca65/expr.c +++ b/src/ca65/expr.c @@ -37,6 +37,7 @@ #include /* common */ +#include "capability.h" #include "check.h" #include "cpu.h" #include "exprdefs.h" @@ -405,6 +406,66 @@ static ExprNode* FuncBlank (void) +static ExprNode* FuncCapability (void) +/* Handle the .CAPABILITY builtin function */ +{ + int Result = 1; + + /* What follows is a comma separated list of identifiers. An empty list is + ** not allowed. + */ + while (1) { + + const char* Name; + capability_t Cap; + + /* We must have an identifier */ + if (CurTok.Tok != TOK_IDENT) { + Error ("Arguments to .CAPABILITY must be identifiers"); + /* Skip tokens until closing paren or end of line */ + while (CurTok.Tok != TOK_RPAREN && !TokIsSep (CurTok.Tok)) { + NextTok (); + } + return GenLiteral0 (); + } + + /* Search for the capability that matches this identifier. Ignore case + ** on the specified capabilities. + */ + UpcaseSVal (); + SB_Terminate (&CurTok.SVal); + Name = SB_GetConstBuf (&CurTok.SVal); + Cap = FindCapability (Name); + + /* Check if the capability is supported */ + if (Cap == CAP_INVALID) { + Error ("Not a valid capability name: %s", Name); + Result = 0; + } else { + /* The pseudo function result is the logical AND of all capabilities + ** given. + */ + if (!CPUHasCap (Cap)) { + Result = 0; + } + } + + /* Skip the capability name */ + NextTok (); + + /* Handle end of list or next capability */ + if (CurTok.Tok != TOK_COMMA) { + break; + } + NextTok (); + } + + /* Done */ + return GenLiteralExpr (Result); +} + + + static ExprNode* FuncConst (void) /* Handle the .CONST builtin function */ { @@ -484,9 +545,10 @@ static ExprNode* FuncIsMnemonic (void) if (FindMacro (&CurTok.SVal) == 0) { Instr = FindInstruction (&CurTok.SVal); } - } - else { - /* Macros and symbols may NOT use the names of instructions, so just check for the instruction */ + } else { + /* Macros and symbols may NOT use the names of instructions, so + ** just check for the instruction. + */ Instr = FindInstruction (&CurTok.SVal); } } @@ -532,7 +594,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) token_t Term = GetTokListTerm (TOK_COMMA); while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -570,7 +632,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) Node = Root; while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -1129,6 +1191,10 @@ static ExprNode* Factor (void) N = Function (FuncBlank); break; + case TOK_CAP: + N = Function (FuncCapability); + break; + case TOK_CONST: N = Function (FuncConst); break; diff --git a/src/ca65/pseudo.c b/src/ca65/pseudo.c index e6e10df2c..301129583 100644 --- a/src/ca65/pseudo.c +++ b/src/ca65/pseudo.c @@ -2114,7 +2114,8 @@ struct CtrlDesc { }; /* NOTE: .AND, .BITAND, .BITNOT, .BITOR, .BITXOR, .MOD, .NOT, .OR, .SHL, .SHR - and .XOR do NOT go into this table */ +** and .XOR do NOT go into this table. +*/ #define PSEUDO_COUNT (sizeof (CtrlCmdTab) / sizeof (CtrlCmdTab [0])) static CtrlDesc CtrlCmdTab [] = { { ccNone, DoA16 }, /* .A16 */ @@ -2132,6 +2133,7 @@ static CtrlDesc CtrlCmdTab [] = { { ccNone, DoUnexpected }, /* .BLANK */ { ccNone, DoBss }, /* .BSS */ { ccNone, DoByte }, /* .BYT, .BYTE */ + { ccNone, DoUnexpected }, /* .CAP */ { ccNone, DoCase }, /* .CASE */ { ccNone, DoCharMap }, /* .CHARMAP */ { ccNone, DoCode }, /* .CODE */ diff --git a/src/ca65/scanner.c b/src/ca65/scanner.c index f3892d32e..a2a72a149 100644 --- a/src/ca65/scanner.c +++ b/src/ca65/scanner.c @@ -158,6 +158,8 @@ struct DotKeyword { { ".BSS", TOK_BSS }, { ".BYT", TOK_BYTE }, { ".BYTE", TOK_BYTE }, + { ".CAP", TOK_CAP }, + { ".CAPABILITY", TOK_CAP }, { ".CASE", TOK_CASE }, { ".CHARMAP", TOK_CHARMAP }, { ".CODE", TOK_CODE }, diff --git a/src/ca65/token.h b/src/ca65/token.h index e2b223880..b40534d79 100644 --- a/src/ca65/token.h +++ b/src/ca65/token.h @@ -137,6 +137,7 @@ typedef enum token_t { TOK_BLANK, TOK_BSS, TOK_BYTE, + TOK_CAP, TOK_CASE, TOK_CHARMAP, TOK_CODE, diff --git a/src/common/capability.c b/src/common/capability.c new file mode 100644 index 000000000..f66205e7c --- /dev/null +++ b/src/common/capability.c @@ -0,0 +1,90 @@ +/*****************************************************************************/ +/* */ +/* capability.c */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#include + +/* ca65 */ +#include "capability.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* List of dot keywords with the corresponding ids. */ +/* CAUTION: table must be sorted for bsearch. */ +struct Capability { + const char* Key; + capability_t Cap; +} Capabilities [] = { +/* BEGIN SORTED.SH */ + { "CPU_HAS_BRA8", CAP_CPU_HAS_BRA8 }, + { "CPU_HAS_INA", CAP_CPU_HAS_INA }, + { "CPU_HAS_PUSHXY", CAP_CPU_HAS_PUSHXY }, + { "CPU_HAS_STZ", CAP_CPU_HAS_STZ }, + { "CPU_HAS_ZPIND", CAP_CPU_HAS_ZPIND }, +/* END SORTED.SH */ +}; +#define CAP_TABLE_SIZE (sizeof (Capabilities) / sizeof (Capabilities [0])) + + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static int CmpCapability (const void* K1, const void* K2) +/* Compare function for the capability search */ +{ + return strcmp (((struct Capability*)K1)->Key, ((struct Capability*)K2)->Key); +} + + + +capability_t FindCapability (const char* Name) +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ +{ + const struct Capability K = { Name, 0 }; + const struct Capability* C = bsearch (&K, Capabilities, CAP_TABLE_SIZE, + sizeof (Capabilities [0]), + CmpCapability); + return (C == 0)? CAP_INVALID : C->Cap; +} diff --git a/src/common/capability.h b/src/common/capability.h new file mode 100644 index 000000000..011e2164f --- /dev/null +++ b/src/common/capability.h @@ -0,0 +1,78 @@ +/*****************************************************************************/ +/* */ +/* capability.h */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#ifndef CAPABILITY_H +#define CAPABILITY_H + + + +/* common */ +#include "strbuf.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Numeric codes for capabilities */ +enum capability_t { + CAP_INVALID = -1, + CAP_CPU_HAS_BRA8 = 0, /* CPU has a BRA 8-bit instruction */ + CAP_CPU_HAS_INA = 1, /* CPU has DEA/INA */ + CAP_CPU_HAS_PUSHXY = 2, /* CPU has PHX/PHY/PLX/PLY */ + CAP_CPU_HAS_ZPIND = 3, /* CPU has "(zp)" mode (no offset) */ + CAP_CPU_HAS_STZ = 4, /* CPU has "store zero" (!) instruction */ +}; +typedef enum capability_t capability_t; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +capability_t FindCapability (const char* Name); +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ + + + +/* End of capability.h */ + +#endif diff --git a/src/common/cpu.c b/src/common/cpu.c index 252283211..cdc8e52cc 100644 --- a/src/common/cpu.c +++ b/src/common/cpu.c @@ -33,6 +33,8 @@ +#include + /* common */ #include "addrsize.h" #include "check.h" @@ -90,6 +92,77 @@ const unsigned CPUIsets[CPU_COUNT] = { CPU_ISET_65CE02 | CPU_ISET_6502 | CPU_ISET_65C02, }; +/* Defines for capabilities. Currently the entries are uint32_ts but the table +** is deliberately hidden from the outside so it can be extended to 64 bit or +** even more. +*/ +#define CAP_NONE UINT32_C (0) +#define CAP_6502 UINT32_C (0) +#define CAP_6502X UINT32_C (0) +#define CAP_6502DTV UINT32_C (0) +#define CAP_65SC02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65816 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_SWEET16 UINT32_C (0) +#define CAP_HUC6280 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_M740 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA)) +#define CAP_4510 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_45GS02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_W65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_65CE02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) + +/* Table containing one capability entry per CPU */ +static const uint64_t CPUCaps[CPU_COUNT] = { + CAP_NONE, /* CPU_NONE */ + CAP_6502, /* CPU_6502 */ + CAP_6502X, /* CPU_6502X */ + CAP_6502DTV, /* CPU_6502DTV */ + CAP_65SC02, /* CPU_65SC02 */ + CAP_65C02, /* CPU_65C02 */ + CAP_65816, /* CPU_65816 */ + CAP_SWEET16, /* CPU_SWEET16 */ + CAP_HUC6280, /* CPU_HUC6280 */ + CAP_M740, /* CPU_M740 */ + CAP_4510, /* CPU_4510 */ + CAP_45GS02, /* CPU_45GS02 */ + CAP_W65C02, /* CPU_W65C02 */ + CAP_65CE02, /* CPU_65CE02 */ +}; + /*****************************************************************************/ @@ -148,3 +221,12 @@ cpu_t FindCPU (const char* Name) /* Not found */ return CPU_UNKNOWN; } + + + +int CPUHasCap (capability_t Cap) +/* Check if the current CPU has the given capability */ +{ + PRECONDITION (CPU >= 0 && CPU < CPU_COUNT); + return (CPUCaps[CPU] & (UINT32_C (1) << Cap)) != 0; +} diff --git a/src/common/cpu.h b/src/common/cpu.h index 4202ed573..0a7c853ac 100644 --- a/src/common/cpu.h +++ b/src/common/cpu.h @@ -38,6 +38,11 @@ +/* common */ +#include "capability.h" + + + /*****************************************************************************/ /* Data */ /*****************************************************************************/ @@ -107,6 +112,9 @@ cpu_t FindCPU (const char* Name); ** the given name is no valid target. */ +int CPUHasCap (capability_t Cap); +/* Check if the current CPU has the given capability */ + /* End of cpu.h */ diff --git a/test/asm/listing/110-capabilities.s b/test/asm/listing/110-capabilities.s new file mode 100644 index 000000000..e3e306ca7 --- /dev/null +++ b/test/asm/listing/110-capabilities.s @@ -0,0 +1,39 @@ + +; Error: Arguments to .CAPABILITY must be identifiers +.if .cap() +.endif + +; Error: Arguments to .CAPABILITY must be identifiers +; Error: ')' expected +.if .cap( +.endif + +; Error: Not a valid capability name: CPU_HAS_BR +.if .cap(cpu_has_br) +.endif + +; Error: ')' expected +; Error: Unexpected trailing garbage characters +.if .cap(cpu_has_bra8 cpu_has_bra8) +.endif + +; Ok +.if .cap(cpu_has_bra8, CPU_HAS_PUSHXY, CPU_HAS_STZ, CPU_HAS_INA) +.endif + +.setcpu "65SC02" +.if !.cap(cpu_has_bra8) +.error "Assembler says 65SC02 has no 8 bit bra" +.endif +.if !.cap(cpu_has_PUSHXY) +.error "Assembler says 65SC02 has no phx" +.endif +.if !.cap(cpu_has_STZ) +.error "Assembler says 65SC02 has no stz" +.endif +.if !.cap(cpu_has_INA) +.error "Assembler says 65SC02 has no ina" +.endif + + + diff --git a/test/asm/listing/control/110-capabilities.err b/test/asm/listing/control/110-capabilities.err new file mode 100644 index 000000000..e69de29bb diff --git a/test/asm/listing/ref/110-capabilities.err2-ref b/test/asm/listing/ref/110-capabilities.err2-ref new file mode 100644 index 000000000..e00e60e17 --- /dev/null +++ b/test/asm/listing/ref/110-capabilities.err2-ref @@ -0,0 +1,6 @@ +110-capabilities.s:3: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: ')' expected +110-capabilities.s:12: Error: Not a valid capability name: CPU_HAS_BR +110-capabilities.s:17: Error: ')' expected +110-capabilities.s:17: Error: Unexpected trailing garbage characters
+ + Builtin function. The function allows to check for capabilities of the + currently selected CPU or target system. It must be called with a comma + separated list of identifiers and returns non zero if all of the given + capabilities are available. Otherwise it returns zero. + + Existing capabilities are: + + + + CPU_HAS_BRA8 + Checks for the availability of a short (8 bit) branch. + + CPU_HAS_INA + Checks for the availability of accu inc/dec instructions. + + CPU_HAS_PUSHXY + Checks for the capability to push and pop the X and Y registers. + + CPU_HAS_ZPIND + Checks for the availability of the "zeropage indirect" addressing mode. + + CPU_HAS_STZ + Checks for the availability of the "store zero" instruction. + + + + Case is ignored when checking the identifiers. The + .if .cap(CPU_HAS_BRA, CPU_HAS_PUSHXY) + phx + bra L1 + .else + txa + pha + jmp L1 + .endif + + + See also: + + + .CONCAT Builtin string function. The function allows to concatenate a list of string diff --git a/src/ca65/expr.c b/src/ca65/expr.c index 5dcf5ca71..42b1a369b 100644 --- a/src/ca65/expr.c +++ b/src/ca65/expr.c @@ -37,6 +37,7 @@ #include /* common */ +#include "capability.h" #include "check.h" #include "cpu.h" #include "exprdefs.h" @@ -405,6 +406,66 @@ static ExprNode* FuncBlank (void) +static ExprNode* FuncCapability (void) +/* Handle the .CAPABILITY builtin function */ +{ + int Result = 1; + + /* What follows is a comma separated list of identifiers. An empty list is + ** not allowed. + */ + while (1) { + + const char* Name; + capability_t Cap; + + /* We must have an identifier */ + if (CurTok.Tok != TOK_IDENT) { + Error ("Arguments to .CAPABILITY must be identifiers"); + /* Skip tokens until closing paren or end of line */ + while (CurTok.Tok != TOK_RPAREN && !TokIsSep (CurTok.Tok)) { + NextTok (); + } + return GenLiteral0 (); + } + + /* Search for the capability that matches this identifier. Ignore case + ** on the specified capabilities. + */ + UpcaseSVal (); + SB_Terminate (&CurTok.SVal); + Name = SB_GetConstBuf (&CurTok.SVal); + Cap = FindCapability (Name); + + /* Check if the capability is supported */ + if (Cap == CAP_INVALID) { + Error ("Not a valid capability name: %s", Name); + Result = 0; + } else { + /* The pseudo function result is the logical AND of all capabilities + ** given. + */ + if (!CPUHasCap (Cap)) { + Result = 0; + } + } + + /* Skip the capability name */ + NextTok (); + + /* Handle end of list or next capability */ + if (CurTok.Tok != TOK_COMMA) { + break; + } + NextTok (); + } + + /* Done */ + return GenLiteralExpr (Result); +} + + + static ExprNode* FuncConst (void) /* Handle the .CONST builtin function */ { @@ -484,9 +545,10 @@ static ExprNode* FuncIsMnemonic (void) if (FindMacro (&CurTok.SVal) == 0) { Instr = FindInstruction (&CurTok.SVal); } - } - else { - /* Macros and symbols may NOT use the names of instructions, so just check for the instruction */ + } else { + /* Macros and symbols may NOT use the names of instructions, so + ** just check for the instruction. + */ Instr = FindInstruction (&CurTok.SVal); } } @@ -532,7 +594,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) token_t Term = GetTokListTerm (TOK_COMMA); while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -570,7 +632,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) Node = Root; while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -1129,6 +1191,10 @@ static ExprNode* Factor (void) N = Function (FuncBlank); break; + case TOK_CAP: + N = Function (FuncCapability); + break; + case TOK_CONST: N = Function (FuncConst); break; diff --git a/src/ca65/pseudo.c b/src/ca65/pseudo.c index e6e10df2c..301129583 100644 --- a/src/ca65/pseudo.c +++ b/src/ca65/pseudo.c @@ -2114,7 +2114,8 @@ struct CtrlDesc { }; /* NOTE: .AND, .BITAND, .BITNOT, .BITOR, .BITXOR, .MOD, .NOT, .OR, .SHL, .SHR - and .XOR do NOT go into this table */ +** and .XOR do NOT go into this table. +*/ #define PSEUDO_COUNT (sizeof (CtrlCmdTab) / sizeof (CtrlCmdTab [0])) static CtrlDesc CtrlCmdTab [] = { { ccNone, DoA16 }, /* .A16 */ @@ -2132,6 +2133,7 @@ static CtrlDesc CtrlCmdTab [] = { { ccNone, DoUnexpected }, /* .BLANK */ { ccNone, DoBss }, /* .BSS */ { ccNone, DoByte }, /* .BYT, .BYTE */ + { ccNone, DoUnexpected }, /* .CAP */ { ccNone, DoCase }, /* .CASE */ { ccNone, DoCharMap }, /* .CHARMAP */ { ccNone, DoCode }, /* .CODE */ diff --git a/src/ca65/scanner.c b/src/ca65/scanner.c index f3892d32e..a2a72a149 100644 --- a/src/ca65/scanner.c +++ b/src/ca65/scanner.c @@ -158,6 +158,8 @@ struct DotKeyword { { ".BSS", TOK_BSS }, { ".BYT", TOK_BYTE }, { ".BYTE", TOK_BYTE }, + { ".CAP", TOK_CAP }, + { ".CAPABILITY", TOK_CAP }, { ".CASE", TOK_CASE }, { ".CHARMAP", TOK_CHARMAP }, { ".CODE", TOK_CODE }, diff --git a/src/ca65/token.h b/src/ca65/token.h index e2b223880..b40534d79 100644 --- a/src/ca65/token.h +++ b/src/ca65/token.h @@ -137,6 +137,7 @@ typedef enum token_t { TOK_BLANK, TOK_BSS, TOK_BYTE, + TOK_CAP, TOK_CASE, TOK_CHARMAP, TOK_CODE, diff --git a/src/common/capability.c b/src/common/capability.c new file mode 100644 index 000000000..f66205e7c --- /dev/null +++ b/src/common/capability.c @@ -0,0 +1,90 @@ +/*****************************************************************************/ +/* */ +/* capability.c */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#include + +/* ca65 */ +#include "capability.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* List of dot keywords with the corresponding ids. */ +/* CAUTION: table must be sorted for bsearch. */ +struct Capability { + const char* Key; + capability_t Cap; +} Capabilities [] = { +/* BEGIN SORTED.SH */ + { "CPU_HAS_BRA8", CAP_CPU_HAS_BRA8 }, + { "CPU_HAS_INA", CAP_CPU_HAS_INA }, + { "CPU_HAS_PUSHXY", CAP_CPU_HAS_PUSHXY }, + { "CPU_HAS_STZ", CAP_CPU_HAS_STZ }, + { "CPU_HAS_ZPIND", CAP_CPU_HAS_ZPIND }, +/* END SORTED.SH */ +}; +#define CAP_TABLE_SIZE (sizeof (Capabilities) / sizeof (Capabilities [0])) + + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static int CmpCapability (const void* K1, const void* K2) +/* Compare function for the capability search */ +{ + return strcmp (((struct Capability*)K1)->Key, ((struct Capability*)K2)->Key); +} + + + +capability_t FindCapability (const char* Name) +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ +{ + const struct Capability K = { Name, 0 }; + const struct Capability* C = bsearch (&K, Capabilities, CAP_TABLE_SIZE, + sizeof (Capabilities [0]), + CmpCapability); + return (C == 0)? CAP_INVALID : C->Cap; +} diff --git a/src/common/capability.h b/src/common/capability.h new file mode 100644 index 000000000..011e2164f --- /dev/null +++ b/src/common/capability.h @@ -0,0 +1,78 @@ +/*****************************************************************************/ +/* */ +/* capability.h */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#ifndef CAPABILITY_H +#define CAPABILITY_H + + + +/* common */ +#include "strbuf.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Numeric codes for capabilities */ +enum capability_t { + CAP_INVALID = -1, + CAP_CPU_HAS_BRA8 = 0, /* CPU has a BRA 8-bit instruction */ + CAP_CPU_HAS_INA = 1, /* CPU has DEA/INA */ + CAP_CPU_HAS_PUSHXY = 2, /* CPU has PHX/PHY/PLX/PLY */ + CAP_CPU_HAS_ZPIND = 3, /* CPU has "(zp)" mode (no offset) */ + CAP_CPU_HAS_STZ = 4, /* CPU has "store zero" (!) instruction */ +}; +typedef enum capability_t capability_t; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +capability_t FindCapability (const char* Name); +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ + + + +/* End of capability.h */ + +#endif diff --git a/src/common/cpu.c b/src/common/cpu.c index 252283211..cdc8e52cc 100644 --- a/src/common/cpu.c +++ b/src/common/cpu.c @@ -33,6 +33,8 @@ +#include + /* common */ #include "addrsize.h" #include "check.h" @@ -90,6 +92,77 @@ const unsigned CPUIsets[CPU_COUNT] = { CPU_ISET_65CE02 | CPU_ISET_6502 | CPU_ISET_65C02, }; +/* Defines for capabilities. Currently the entries are uint32_ts but the table +** is deliberately hidden from the outside so it can be extended to 64 bit or +** even more. +*/ +#define CAP_NONE UINT32_C (0) +#define CAP_6502 UINT32_C (0) +#define CAP_6502X UINT32_C (0) +#define CAP_6502DTV UINT32_C (0) +#define CAP_65SC02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65816 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_SWEET16 UINT32_C (0) +#define CAP_HUC6280 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_M740 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA)) +#define CAP_4510 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_45GS02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_W65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_65CE02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) + +/* Table containing one capability entry per CPU */ +static const uint64_t CPUCaps[CPU_COUNT] = { + CAP_NONE, /* CPU_NONE */ + CAP_6502, /* CPU_6502 */ + CAP_6502X, /* CPU_6502X */ + CAP_6502DTV, /* CPU_6502DTV */ + CAP_65SC02, /* CPU_65SC02 */ + CAP_65C02, /* CPU_65C02 */ + CAP_65816, /* CPU_65816 */ + CAP_SWEET16, /* CPU_SWEET16 */ + CAP_HUC6280, /* CPU_HUC6280 */ + CAP_M740, /* CPU_M740 */ + CAP_4510, /* CPU_4510 */ + CAP_45GS02, /* CPU_45GS02 */ + CAP_W65C02, /* CPU_W65C02 */ + CAP_65CE02, /* CPU_65CE02 */ +}; + /*****************************************************************************/ @@ -148,3 +221,12 @@ cpu_t FindCPU (const char* Name) /* Not found */ return CPU_UNKNOWN; } + + + +int CPUHasCap (capability_t Cap) +/* Check if the current CPU has the given capability */ +{ + PRECONDITION (CPU >= 0 && CPU < CPU_COUNT); + return (CPUCaps[CPU] & (UINT32_C (1) << Cap)) != 0; +} diff --git a/src/common/cpu.h b/src/common/cpu.h index 4202ed573..0a7c853ac 100644 --- a/src/common/cpu.h +++ b/src/common/cpu.h @@ -38,6 +38,11 @@ +/* common */ +#include "capability.h" + + + /*****************************************************************************/ /* Data */ /*****************************************************************************/ @@ -107,6 +112,9 @@ cpu_t FindCPU (const char* Name); ** the given name is no valid target. */ +int CPUHasCap (capability_t Cap); +/* Check if the current CPU has the given capability */ + /* End of cpu.h */ diff --git a/test/asm/listing/110-capabilities.s b/test/asm/listing/110-capabilities.s new file mode 100644 index 000000000..e3e306ca7 --- /dev/null +++ b/test/asm/listing/110-capabilities.s @@ -0,0 +1,39 @@ + +; Error: Arguments to .CAPABILITY must be identifiers +.if .cap() +.endif + +; Error: Arguments to .CAPABILITY must be identifiers +; Error: ')' expected +.if .cap( +.endif + +; Error: Not a valid capability name: CPU_HAS_BR +.if .cap(cpu_has_br) +.endif + +; Error: ')' expected +; Error: Unexpected trailing garbage characters +.if .cap(cpu_has_bra8 cpu_has_bra8) +.endif + +; Ok +.if .cap(cpu_has_bra8, CPU_HAS_PUSHXY, CPU_HAS_STZ, CPU_HAS_INA) +.endif + +.setcpu "65SC02" +.if !.cap(cpu_has_bra8) +.error "Assembler says 65SC02 has no 8 bit bra" +.endif +.if !.cap(cpu_has_PUSHXY) +.error "Assembler says 65SC02 has no phx" +.endif +.if !.cap(cpu_has_STZ) +.error "Assembler says 65SC02 has no stz" +.endif +.if !.cap(cpu_has_INA) +.error "Assembler says 65SC02 has no ina" +.endif + + + diff --git a/test/asm/listing/control/110-capabilities.err b/test/asm/listing/control/110-capabilities.err new file mode 100644 index 000000000..e69de29bb diff --git a/test/asm/listing/ref/110-capabilities.err2-ref b/test/asm/listing/ref/110-capabilities.err2-ref new file mode 100644 index 000000000..e00e60e17 --- /dev/null +++ b/test/asm/listing/ref/110-capabilities.err2-ref @@ -0,0 +1,6 @@ +110-capabilities.s:3: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: ')' expected +110-capabilities.s:12: Error: Not a valid capability name: CPU_HAS_BR +110-capabilities.s:17: Error: ')' expected +110-capabilities.s:17: Error: Unexpected trailing garbage characters
Builtin string function. The function allows to concatenate a list of string diff --git a/src/ca65/expr.c b/src/ca65/expr.c index 5dcf5ca71..42b1a369b 100644 --- a/src/ca65/expr.c +++ b/src/ca65/expr.c @@ -37,6 +37,7 @@ #include /* common */ +#include "capability.h" #include "check.h" #include "cpu.h" #include "exprdefs.h" @@ -405,6 +406,66 @@ static ExprNode* FuncBlank (void) +static ExprNode* FuncCapability (void) +/* Handle the .CAPABILITY builtin function */ +{ + int Result = 1; + + /* What follows is a comma separated list of identifiers. An empty list is + ** not allowed. + */ + while (1) { + + const char* Name; + capability_t Cap; + + /* We must have an identifier */ + if (CurTok.Tok != TOK_IDENT) { + Error ("Arguments to .CAPABILITY must be identifiers"); + /* Skip tokens until closing paren or end of line */ + while (CurTok.Tok != TOK_RPAREN && !TokIsSep (CurTok.Tok)) { + NextTok (); + } + return GenLiteral0 (); + } + + /* Search for the capability that matches this identifier. Ignore case + ** on the specified capabilities. + */ + UpcaseSVal (); + SB_Terminate (&CurTok.SVal); + Name = SB_GetConstBuf (&CurTok.SVal); + Cap = FindCapability (Name); + + /* Check if the capability is supported */ + if (Cap == CAP_INVALID) { + Error ("Not a valid capability name: %s", Name); + Result = 0; + } else { + /* The pseudo function result is the logical AND of all capabilities + ** given. + */ + if (!CPUHasCap (Cap)) { + Result = 0; + } + } + + /* Skip the capability name */ + NextTok (); + + /* Handle end of list or next capability */ + if (CurTok.Tok != TOK_COMMA) { + break; + } + NextTok (); + } + + /* Done */ + return GenLiteralExpr (Result); +} + + + static ExprNode* FuncConst (void) /* Handle the .CONST builtin function */ { @@ -484,9 +545,10 @@ static ExprNode* FuncIsMnemonic (void) if (FindMacro (&CurTok.SVal) == 0) { Instr = FindInstruction (&CurTok.SVal); } - } - else { - /* Macros and symbols may NOT use the names of instructions, so just check for the instruction */ + } else { + /* Macros and symbols may NOT use the names of instructions, so + ** just check for the instruction. + */ Instr = FindInstruction (&CurTok.SVal); } } @@ -532,7 +594,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) token_t Term = GetTokListTerm (TOK_COMMA); while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -570,7 +632,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel) Node = Root; while (CurTok.Tok != Term) { - /* We may not end-of-line of end-of-file here */ + /* We may not end-of-line or end-of-file here */ if (TokIsSep (CurTok.Tok)) { Error ("Unexpected end of line"); return GenLiteral0 (); @@ -1129,6 +1191,10 @@ static ExprNode* Factor (void) N = Function (FuncBlank); break; + case TOK_CAP: + N = Function (FuncCapability); + break; + case TOK_CONST: N = Function (FuncConst); break; diff --git a/src/ca65/pseudo.c b/src/ca65/pseudo.c index e6e10df2c..301129583 100644 --- a/src/ca65/pseudo.c +++ b/src/ca65/pseudo.c @@ -2114,7 +2114,8 @@ struct CtrlDesc { }; /* NOTE: .AND, .BITAND, .BITNOT, .BITOR, .BITXOR, .MOD, .NOT, .OR, .SHL, .SHR - and .XOR do NOT go into this table */ +** and .XOR do NOT go into this table. +*/ #define PSEUDO_COUNT (sizeof (CtrlCmdTab) / sizeof (CtrlCmdTab [0])) static CtrlDesc CtrlCmdTab [] = { { ccNone, DoA16 }, /* .A16 */ @@ -2132,6 +2133,7 @@ static CtrlDesc CtrlCmdTab [] = { { ccNone, DoUnexpected }, /* .BLANK */ { ccNone, DoBss }, /* .BSS */ { ccNone, DoByte }, /* .BYT, .BYTE */ + { ccNone, DoUnexpected }, /* .CAP */ { ccNone, DoCase }, /* .CASE */ { ccNone, DoCharMap }, /* .CHARMAP */ { ccNone, DoCode }, /* .CODE */ diff --git a/src/ca65/scanner.c b/src/ca65/scanner.c index f3892d32e..a2a72a149 100644 --- a/src/ca65/scanner.c +++ b/src/ca65/scanner.c @@ -158,6 +158,8 @@ struct DotKeyword { { ".BSS", TOK_BSS }, { ".BYT", TOK_BYTE }, { ".BYTE", TOK_BYTE }, + { ".CAP", TOK_CAP }, + { ".CAPABILITY", TOK_CAP }, { ".CASE", TOK_CASE }, { ".CHARMAP", TOK_CHARMAP }, { ".CODE", TOK_CODE }, diff --git a/src/ca65/token.h b/src/ca65/token.h index e2b223880..b40534d79 100644 --- a/src/ca65/token.h +++ b/src/ca65/token.h @@ -137,6 +137,7 @@ typedef enum token_t { TOK_BLANK, TOK_BSS, TOK_BYTE, + TOK_CAP, TOK_CASE, TOK_CHARMAP, TOK_CODE, diff --git a/src/common/capability.c b/src/common/capability.c new file mode 100644 index 000000000..f66205e7c --- /dev/null +++ b/src/common/capability.c @@ -0,0 +1,90 @@ +/*****************************************************************************/ +/* */ +/* capability.c */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#include + +/* ca65 */ +#include "capability.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* List of dot keywords with the corresponding ids. */ +/* CAUTION: table must be sorted for bsearch. */ +struct Capability { + const char* Key; + capability_t Cap; +} Capabilities [] = { +/* BEGIN SORTED.SH */ + { "CPU_HAS_BRA8", CAP_CPU_HAS_BRA8 }, + { "CPU_HAS_INA", CAP_CPU_HAS_INA }, + { "CPU_HAS_PUSHXY", CAP_CPU_HAS_PUSHXY }, + { "CPU_HAS_STZ", CAP_CPU_HAS_STZ }, + { "CPU_HAS_ZPIND", CAP_CPU_HAS_ZPIND }, +/* END SORTED.SH */ +}; +#define CAP_TABLE_SIZE (sizeof (Capabilities) / sizeof (Capabilities [0])) + + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +static int CmpCapability (const void* K1, const void* K2) +/* Compare function for the capability search */ +{ + return strcmp (((struct Capability*)K1)->Key, ((struct Capability*)K2)->Key); +} + + + +capability_t FindCapability (const char* Name) +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ +{ + const struct Capability K = { Name, 0 }; + const struct Capability* C = bsearch (&K, Capabilities, CAP_TABLE_SIZE, + sizeof (Capabilities [0]), + CmpCapability); + return (C == 0)? CAP_INVALID : C->Cap; +} diff --git a/src/common/capability.h b/src/common/capability.h new file mode 100644 index 000000000..011e2164f --- /dev/null +++ b/src/common/capability.h @@ -0,0 +1,78 @@ +/*****************************************************************************/ +/* */ +/* capability.h */ +/* */ +/* Handle CPU or target capabilities */ +/* */ +/* */ +/* */ +/* (C) 2026, Kugelfuhr */ +/* */ +/* */ +/* This software is provided 'as-is', without any expressed or implied */ +/* warranty. In no event will the authors be held liable for any damages */ +/* arising from the use of this software. */ +/* */ +/* Permission is granted to anyone to use this software for any purpose, */ +/* including commercial applications, and to alter it and redistribute it */ +/* freely, subject to the following restrictions: */ +/* */ +/* 1. The origin of this software must not be misrepresented; you must not */ +/* claim that you wrote the original software. If you use this software */ +/* in a product, an acknowledgment in the product documentation would be */ +/* appreciated but is not required. */ +/* 2. Altered source versions must be plainly marked as such, and must not */ +/* be misrepresented as being the original software. */ +/* 3. This notice may not be removed or altered from any source */ +/* distribution. */ +/* */ +/*****************************************************************************/ + + + +#ifndef CAPABILITY_H +#define CAPABILITY_H + + + +/* common */ +#include "strbuf.h" + + + +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Numeric codes for capabilities */ +enum capability_t { + CAP_INVALID = -1, + CAP_CPU_HAS_BRA8 = 0, /* CPU has a BRA 8-bit instruction */ + CAP_CPU_HAS_INA = 1, /* CPU has DEA/INA */ + CAP_CPU_HAS_PUSHXY = 2, /* CPU has PHX/PHY/PLX/PLY */ + CAP_CPU_HAS_ZPIND = 3, /* CPU has "(zp)" mode (no offset) */ + CAP_CPU_HAS_STZ = 4, /* CPU has "store zero" (!) instruction */ +}; +typedef enum capability_t capability_t; + + + +/*****************************************************************************/ +/* Code */ +/*****************************************************************************/ + + + +capability_t FindCapability (const char* Name); +/* Find the capability with the given name. Returns CAP_INVALID if there is no +** capability with the given name and a capability code >= 0 instead. The +** capability name is expected in upper case. +*/ + + + +/* End of capability.h */ + +#endif diff --git a/src/common/cpu.c b/src/common/cpu.c index 252283211..cdc8e52cc 100644 --- a/src/common/cpu.c +++ b/src/common/cpu.c @@ -33,6 +33,8 @@ +#include + /* common */ #include "addrsize.h" #include "check.h" @@ -90,6 +92,77 @@ const unsigned CPUIsets[CPU_COUNT] = { CPU_ISET_65CE02 | CPU_ISET_6502 | CPU_ISET_65C02, }; +/* Defines for capabilities. Currently the entries are uint32_ts but the table +** is deliberately hidden from the outside so it can be extended to 64 bit or +** even more. +*/ +#define CAP_NONE UINT32_C (0) +#define CAP_6502 UINT32_C (0) +#define CAP_6502X UINT32_C (0) +#define CAP_6502DTV UINT32_C (0) +#define CAP_65SC02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_65816 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_SWEET16 UINT32_C (0) +#define CAP_HUC6280 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY) | \ + (UINT32_C (1) << CAP_CPU_HAS_ZPIND) | \ + (UINT32_C (1) << CAP_CPU_HAS_STZ)) +#define CAP_M740 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA)) +#define CAP_4510 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_45GS02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_W65C02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) +#define CAP_65CE02 \ + ((UINT32_C (1) << CAP_CPU_HAS_BRA8) | \ + (UINT32_C (1) << CAP_CPU_HAS_INA) | \ + (UINT32_C (1) << CAP_CPU_HAS_PUSHXY)) + +/* Table containing one capability entry per CPU */ +static const uint64_t CPUCaps[CPU_COUNT] = { + CAP_NONE, /* CPU_NONE */ + CAP_6502, /* CPU_6502 */ + CAP_6502X, /* CPU_6502X */ + CAP_6502DTV, /* CPU_6502DTV */ + CAP_65SC02, /* CPU_65SC02 */ + CAP_65C02, /* CPU_65C02 */ + CAP_65816, /* CPU_65816 */ + CAP_SWEET16, /* CPU_SWEET16 */ + CAP_HUC6280, /* CPU_HUC6280 */ + CAP_M740, /* CPU_M740 */ + CAP_4510, /* CPU_4510 */ + CAP_45GS02, /* CPU_45GS02 */ + CAP_W65C02, /* CPU_W65C02 */ + CAP_65CE02, /* CPU_65CE02 */ +}; + /*****************************************************************************/ @@ -148,3 +221,12 @@ cpu_t FindCPU (const char* Name) /* Not found */ return CPU_UNKNOWN; } + + + +int CPUHasCap (capability_t Cap) +/* Check if the current CPU has the given capability */ +{ + PRECONDITION (CPU >= 0 && CPU < CPU_COUNT); + return (CPUCaps[CPU] & (UINT32_C (1) << Cap)) != 0; +} diff --git a/src/common/cpu.h b/src/common/cpu.h index 4202ed573..0a7c853ac 100644 --- a/src/common/cpu.h +++ b/src/common/cpu.h @@ -38,6 +38,11 @@ +/* common */ +#include "capability.h" + + + /*****************************************************************************/ /* Data */ /*****************************************************************************/ @@ -107,6 +112,9 @@ cpu_t FindCPU (const char* Name); ** the given name is no valid target. */ +int CPUHasCap (capability_t Cap); +/* Check if the current CPU has the given capability */ + /* End of cpu.h */ diff --git a/test/asm/listing/110-capabilities.s b/test/asm/listing/110-capabilities.s new file mode 100644 index 000000000..e3e306ca7 --- /dev/null +++ b/test/asm/listing/110-capabilities.s @@ -0,0 +1,39 @@ + +; Error: Arguments to .CAPABILITY must be identifiers +.if .cap() +.endif + +; Error: Arguments to .CAPABILITY must be identifiers +; Error: ')' expected +.if .cap( +.endif + +; Error: Not a valid capability name: CPU_HAS_BR +.if .cap(cpu_has_br) +.endif + +; Error: ')' expected +; Error: Unexpected trailing garbage characters +.if .cap(cpu_has_bra8 cpu_has_bra8) +.endif + +; Ok +.if .cap(cpu_has_bra8, CPU_HAS_PUSHXY, CPU_HAS_STZ, CPU_HAS_INA) +.endif + +.setcpu "65SC02" +.if !.cap(cpu_has_bra8) +.error "Assembler says 65SC02 has no 8 bit bra" +.endif +.if !.cap(cpu_has_PUSHXY) +.error "Assembler says 65SC02 has no phx" +.endif +.if !.cap(cpu_has_STZ) +.error "Assembler says 65SC02 has no stz" +.endif +.if !.cap(cpu_has_INA) +.error "Assembler says 65SC02 has no ina" +.endif + + + diff --git a/test/asm/listing/control/110-capabilities.err b/test/asm/listing/control/110-capabilities.err new file mode 100644 index 000000000..e69de29bb diff --git a/test/asm/listing/ref/110-capabilities.err2-ref b/test/asm/listing/ref/110-capabilities.err2-ref new file mode 100644 index 000000000..e00e60e17 --- /dev/null +++ b/test/asm/listing/ref/110-capabilities.err2-ref @@ -0,0 +1,6 @@ +110-capabilities.s:3: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: Arguments to .CAPABILITY must be identifiers +110-capabilities.s:8: Error: ')' expected +110-capabilities.s:12: Error: Not a valid capability name: CPU_HAS_BR +110-capabilities.s:17: Error: ')' expected +110-capabilities.s:17: Error: Unexpected trailing garbage characters