Add a new .cap pseudo function to the assembler that allows to check for

certain capabilities of the CPU or target system.
This commit is contained in:
Kugel Fuhr
2025-06-30 21:37:43 +02:00
parent a4a24280f2
commit d4e57278c6
12 changed files with 434 additions and 6 deletions

View File

@@ -1423,6 +1423,10 @@ writable.
</verb></tscreen> </verb></tscreen>
See also: <tt><ref id=".CAP" name=".CAP"></tt>
<sect1><tt>.ISIZE</tt><label id=".ISIZE"><p> <sect1><tt>.ISIZE</tt><label id=".ISIZE"><p>
Reading this pseudo variable will return the current size of the Index Reading this pseudo variable will return the current size of the Index
@@ -1594,6 +1598,56 @@ either a string or an expression value.
<sect1><tt>.CAP, .CAPABILITY</tt><label id=".CAP"><p>
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:
<descrip>
<tag><tt>CPU_HAS_BRA8</tt></tag>
Checks for the availability of a short (8 bit) branch.
<tag><tt>CPU_HAS_INA</tt></tag>
Checks for the availability of accu inc/dec instructions.
<tag><tt>CPU_HAS_PUSHXY</tt></tag>
Checks for the capability to push and pop the X and Y registers.
<tag><tt>CPU_HAS_ZPIND</tt></tag>
Checks for the availability of the "zeropage indirect" addressing mode.
<tag><tt>CPU_HAS_STZ</tt></tag>
Checks for the availability of the "store zero" instruction.
</descrip>
Case is ignored when checking the identifiers. The <tt/.cap/ function is
easier to use than checking <tt/.cpu/ and requires no intimate knowledge
of all instruction sets. For more detailed checking <tt/.cpu/ is still
available.
Example:
<tscreen><verb>
.if .cap(CPU_HAS_BRA, CPU_HAS_PUSHXY)
phx
bra L1
.else
txa
pha
jmp L1
.endif
</verb></tscreen>
See also: <tt><ref id=".CPU" name=".CPU"></tt>
<sect1><tt>.CONCAT</tt><label id=".CONCAT"><p> <sect1><tt>.CONCAT</tt><label id=".CONCAT"><p>
Builtin string function. The function allows to concatenate a list of string Builtin string function. The function allows to concatenate a list of string

View File

@@ -37,6 +37,7 @@
#include <time.h> #include <time.h>
/* common */ /* common */
#include "capability.h"
#include "check.h" #include "check.h"
#include "cpu.h" #include "cpu.h"
#include "exprdefs.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) static ExprNode* FuncConst (void)
/* Handle the .CONST builtin function */ /* Handle the .CONST builtin function */
{ {
@@ -484,9 +545,10 @@ static ExprNode* FuncIsMnemonic (void)
if (FindMacro (&CurTok.SVal) == 0) { if (FindMacro (&CurTok.SVal) == 0) {
Instr = FindInstruction (&CurTok.SVal); Instr = FindInstruction (&CurTok.SVal);
} }
} } else {
else { /* Macros and symbols may NOT use the names of instructions, so
/* Macros and symbols may NOT use the names of instructions, so just check for the instruction */ ** just check for the instruction.
*/
Instr = FindInstruction (&CurTok.SVal); Instr = FindInstruction (&CurTok.SVal);
} }
} }
@@ -532,7 +594,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel)
token_t Term = GetTokListTerm (TOK_COMMA); token_t Term = GetTokListTerm (TOK_COMMA);
while (CurTok.Tok != Term) { 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)) { if (TokIsSep (CurTok.Tok)) {
Error ("Unexpected end of line"); Error ("Unexpected end of line");
return GenLiteral0 (); return GenLiteral0 ();
@@ -570,7 +632,7 @@ static ExprNode* DoMatch (enum TC EqualityLevel)
Node = Root; Node = Root;
while (CurTok.Tok != Term) { 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)) { if (TokIsSep (CurTok.Tok)) {
Error ("Unexpected end of line"); Error ("Unexpected end of line");
return GenLiteral0 (); return GenLiteral0 ();
@@ -1129,6 +1191,10 @@ static ExprNode* Factor (void)
N = Function (FuncBlank); N = Function (FuncBlank);
break; break;
case TOK_CAP:
N = Function (FuncCapability);
break;
case TOK_CONST: case TOK_CONST:
N = Function (FuncConst); N = Function (FuncConst);
break; break;

View File

@@ -2114,7 +2114,8 @@ struct CtrlDesc {
}; };
/* NOTE: .AND, .BITAND, .BITNOT, .BITOR, .BITXOR, .MOD, .NOT, .OR, .SHL, .SHR /* 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])) #define PSEUDO_COUNT (sizeof (CtrlCmdTab) / sizeof (CtrlCmdTab [0]))
static CtrlDesc CtrlCmdTab [] = { static CtrlDesc CtrlCmdTab [] = {
{ ccNone, DoA16 }, /* .A16 */ { ccNone, DoA16 }, /* .A16 */
@@ -2132,6 +2133,7 @@ static CtrlDesc CtrlCmdTab [] = {
{ ccNone, DoUnexpected }, /* .BLANK */ { ccNone, DoUnexpected }, /* .BLANK */
{ ccNone, DoBss }, /* .BSS */ { ccNone, DoBss }, /* .BSS */
{ ccNone, DoByte }, /* .BYT, .BYTE */ { ccNone, DoByte }, /* .BYT, .BYTE */
{ ccNone, DoUnexpected }, /* .CAP */
{ ccNone, DoCase }, /* .CASE */ { ccNone, DoCase }, /* .CASE */
{ ccNone, DoCharMap }, /* .CHARMAP */ { ccNone, DoCharMap }, /* .CHARMAP */
{ ccNone, DoCode }, /* .CODE */ { ccNone, DoCode }, /* .CODE */

View File

@@ -158,6 +158,8 @@ struct DotKeyword {
{ ".BSS", TOK_BSS }, { ".BSS", TOK_BSS },
{ ".BYT", TOK_BYTE }, { ".BYT", TOK_BYTE },
{ ".BYTE", TOK_BYTE }, { ".BYTE", TOK_BYTE },
{ ".CAP", TOK_CAP },
{ ".CAPABILITY", TOK_CAP },
{ ".CASE", TOK_CASE }, { ".CASE", TOK_CASE },
{ ".CHARMAP", TOK_CHARMAP }, { ".CHARMAP", TOK_CHARMAP },
{ ".CODE", TOK_CODE }, { ".CODE", TOK_CODE },

View File

@@ -137,6 +137,7 @@ typedef enum token_t {
TOK_BLANK, TOK_BLANK,
TOK_BSS, TOK_BSS,
TOK_BYTE, TOK_BYTE,
TOK_CAP,
TOK_CASE, TOK_CASE,
TOK_CHARMAP, TOK_CHARMAP,
TOK_CODE, TOK_CODE,

90
src/common/capability.c Normal file
View File

@@ -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 <stdlib.h>
/* 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;
}

78
src/common/capability.h Normal file
View File

@@ -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

View File

@@ -33,6 +33,8 @@
#include <stdint.h>
/* common */ /* common */
#include "addrsize.h" #include "addrsize.h"
#include "check.h" #include "check.h"
@@ -90,6 +92,77 @@ const unsigned CPUIsets[CPU_COUNT] = {
CPU_ISET_65CE02 | CPU_ISET_6502 | CPU_ISET_65C02, 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 */ /* Not found */
return CPU_UNKNOWN; 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;
}

View File

@@ -38,6 +38,11 @@
/* common */
#include "capability.h"
/*****************************************************************************/ /*****************************************************************************/
/* Data */ /* Data */
/*****************************************************************************/ /*****************************************************************************/
@@ -107,6 +112,9 @@ cpu_t FindCPU (const char* Name);
** the given name is no valid target. ** 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 */ /* End of cpu.h */

View File

@@ -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

View File

@@ -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