mirror of
https://github.com/vosen/ZLUDA.git
synced 2025-08-11 10:41:44 +00:00
Move all types to a separate module
This commit is contained in:
parent
0112880f27
commit
91dbbb372b
4 changed files with 168 additions and 199 deletions
|
@ -178,9 +178,9 @@ impl SingleOpcodeDefinition {
|
|||
.chain(self.arguments.0.iter().map(|arg| {
|
||||
let name = &arg.ident;
|
||||
let arg_type = if arg.unified {
|
||||
quote! { (ParsedOperand<'input>, bool) }
|
||||
quote! { (ParsedOperandStr<'input>, bool) }
|
||||
} else {
|
||||
quote! { ParsedOperand<'input> }
|
||||
quote! { ParsedOperandStr<'input> }
|
||||
};
|
||||
if arg.optional {
|
||||
quote! { #name : Option<#arg_type> }
|
||||
|
@ -415,7 +415,7 @@ fn emit_parse_function(
|
|||
let code_block = &def.code_block.0;
|
||||
let args = def.function_arguments_declarations();
|
||||
quote! {
|
||||
fn #fn_name<'input>(state: &mut PtxParserState, #(#args),* ) -> Instruction<ParsedOperand<'input>> #code_block
|
||||
fn #fn_name<'input>(state: &mut PtxParserState, #(#args),* ) -> Instruction<ParsedOperandStr<'input>> #code_block
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -506,7 +506,7 @@ fn emit_parse_function(
|
|||
|
||||
#(#fns_)*
|
||||
|
||||
fn parse_instruction<'a, 'input>(stream: &mut PtxParser<'a, 'input>) -> winnow::error::PResult<Instruction<ParsedOperand<'input>>>
|
||||
fn parse_instruction<'a, 'input>(stream: &mut PtxParser<'a, 'input>) -> winnow::error::PResult<Instruction<ParsedOperandStr<'input>>>
|
||||
{
|
||||
use winnow::Parser;
|
||||
use winnow::token::*;
|
||||
|
@ -747,7 +747,7 @@ fn emit_definition_parser(
|
|||
};
|
||||
let operand = {
|
||||
quote! {
|
||||
ParsedOperand::parse
|
||||
ParsedOperandStr::parse
|
||||
}
|
||||
};
|
||||
let post_bracket = if arg.post_bracket {
|
||||
|
|
|
@ -2,11 +2,13 @@ use proc_macro2::TokenStream;
|
|||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
braced, parse::Parse, punctuated::Punctuated, token, Expr, Ident, Token, Type, TypeParam,
|
||||
Visibility,
|
||||
};
|
||||
|
||||
pub mod parser;
|
||||
|
||||
pub struct GenerateInstructionType {
|
||||
pub visibility: Option<Visibility>,
|
||||
pub name: Ident,
|
||||
pub type_parameters: Punctuated<TypeParam, Token![,]>,
|
||||
pub short_parameters: Punctuated<Ident, Token![,]>,
|
||||
|
@ -16,16 +18,17 @@ pub struct GenerateInstructionType {
|
|||
impl GenerateInstructionType {
|
||||
pub fn emit_arg_types(&self, tokens: &mut TokenStream) {
|
||||
for v in self.variants.iter() {
|
||||
v.emit_type(&self.type_parameters, tokens);
|
||||
v.emit_type(&self.visibility, &self.type_parameters, tokens);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_instruction_type(&self, tokens: &mut TokenStream) {
|
||||
let vis = &self.visibility;
|
||||
let type_name = &self.name;
|
||||
let type_parameters = &self.type_parameters;
|
||||
let variants = self.variants.iter().map(|v| v.emit_variant());
|
||||
quote! {
|
||||
enum #type_name<#type_parameters> {
|
||||
#vis enum #type_name<#type_parameters> {
|
||||
#(#variants),*
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +136,11 @@ impl VisitKind {
|
|||
|
||||
impl Parse for GenerateInstructionType {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let visibility = if !input.peek(Token![enum]) {
|
||||
Some(input.parse::<Visibility>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
input.parse::<Token![enum]>()?;
|
||||
let name = input.parse::<Ident>()?;
|
||||
input.parse::<Token![<]>()?;
|
||||
|
@ -146,6 +154,7 @@ impl Parse for GenerateInstructionType {
|
|||
braced!(variants_buffer in input);
|
||||
let variants = variants_buffer.parse_terminated(InstructionVariant::parse, Token![,])?;
|
||||
Ok(Self {
|
||||
visibility,
|
||||
name,
|
||||
type_parameters,
|
||||
short_parameters,
|
||||
|
@ -262,6 +271,7 @@ impl InstructionVariant {
|
|||
|
||||
fn emit_type(
|
||||
&self,
|
||||
vis: &Option<Visibility>,
|
||||
type_parameters: &Punctuated<TypeParam, Token![,]>,
|
||||
tokens: &mut TokenStream,
|
||||
) {
|
||||
|
@ -275,9 +285,9 @@ impl InstructionVariant {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let fields = arguments.fields.iter().map(ArgumentField::emit_field);
|
||||
let fields = arguments.fields.iter().map(|f| f.emit_field(vis));
|
||||
quote! {
|
||||
struct #name #type_parameters {
|
||||
#vis struct #name #type_parameters {
|
||||
#(#fields),*
|
||||
}
|
||||
}
|
||||
|
@ -559,11 +569,11 @@ impl ArgumentField {
|
|||
}
|
||||
}
|
||||
|
||||
fn emit_field(&self) -> TokenStream {
|
||||
fn emit_field(&self, vis: &Option<Visibility>) -> TokenStream {
|
||||
let name = &self.name;
|
||||
let type_ = &self.repr;
|
||||
quote! {
|
||||
#name: #type_
|
||||
#vis #name: #type_
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,113 @@
|
|||
use super::MemScope;
|
||||
use super::{MemScope, ScalarType, VectorPrefix, StateSpace};
|
||||
|
||||
gen::generate_instruction_type!(
|
||||
pub enum Instruction<T> {
|
||||
Mov {
|
||||
type: { &data.typ },
|
||||
data: MovDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src: T
|
||||
}
|
||||
},
|
||||
Ld {
|
||||
type: { &data.typ },
|
||||
data: LdDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src: {
|
||||
repr: T,
|
||||
space: { data.state_space },
|
||||
}
|
||||
}
|
||||
},
|
||||
Add {
|
||||
type: { data.type_().into() },
|
||||
data: ArithDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src1: T,
|
||||
src2: T,
|
||||
}
|
||||
},
|
||||
St {
|
||||
type: { &data.typ },
|
||||
data: StData,
|
||||
arguments<T>: {
|
||||
src1: {
|
||||
repr: T,
|
||||
space: { data.state_space },
|
||||
},
|
||||
src2: T,
|
||||
}
|
||||
},
|
||||
Ret {
|
||||
data: RetData
|
||||
},
|
||||
Trap { }
|
||||
}
|
||||
);
|
||||
|
||||
pub trait Visitor<T> {
|
||||
fn visit(&mut self, args: &T, type_: &Type, space: StateSpace, is_dst: bool);
|
||||
}
|
||||
|
||||
pub trait VisitorMut<T> {
|
||||
fn visit(&mut self, args: &mut T, type_: &Type, space: StateSpace, is_dst: bool);
|
||||
}
|
||||
|
||||
pub trait VisitorMap<From, To> {
|
||||
fn visit(&mut self, args: From, type_: &Type, space: StateSpace, is_dst: bool) -> To;
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Type {
|
||||
// .param.b32 foo;
|
||||
Scalar(ScalarType),
|
||||
// .param.v2.b32 foo;
|
||||
Vector(ScalarType, u8),
|
||||
// .param.b32 foo[4];
|
||||
Array(ScalarType, Vec<u32>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub(crate) fn maybe_vector(vector: Option<VectorPrefix>, scalar: ScalarType) -> Self {
|
||||
match vector {
|
||||
Some(VectorPrefix::V2) => Type::Vector(scalar, 2),
|
||||
Some(VectorPrefix::V4) => Type::Vector(scalar, 4),
|
||||
None => Type::Scalar(scalar),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScalarType> for Type {
|
||||
fn from(value: ScalarType) -> Self {
|
||||
Type::Scalar(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MovDetails {
|
||||
pub typ: super::Type,
|
||||
pub src_is_address: bool,
|
||||
// two fields below are in use by member moves
|
||||
pub dst_width: u8,
|
||||
pub src_width: u8,
|
||||
// This is in use by auto-generated movs
|
||||
pub relaxed_src2_conv: bool,
|
||||
}
|
||||
|
||||
impl MovDetails {
|
||||
pub(crate) fn new(vector: Option<VectorPrefix>, scalar: ScalarType) -> Self {
|
||||
MovDetails {
|
||||
typ: Type::maybe_vector(vector, scalar),
|
||||
src_is_address: false,
|
||||
dst_width: 0,
|
||||
src_width: 0,
|
||||
relaxed_src2_conv: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ParsedOperand<Ident> {
|
||||
|
@ -81,3 +190,24 @@ pub enum RoundingMode {
|
|||
NegativeInf,
|
||||
PositiveInf,
|
||||
}
|
||||
|
||||
pub struct LdDetails {
|
||||
pub qualifier: LdStQualifier,
|
||||
pub state_space: StateSpace,
|
||||
pub caching: LdCacheOperator,
|
||||
pub typ: Type,
|
||||
pub non_coherent: bool,
|
||||
}
|
||||
|
||||
|
||||
pub struct StData {
|
||||
pub qualifier: LdStQualifier,
|
||||
pub state_space: StateSpace,
|
||||
pub caching: StCacheOperator,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RetData {
|
||||
pub uniform: bool,
|
||||
}
|
||||
|
|
|
@ -12,176 +12,7 @@ use winnow::{
|
|||
use winnow::{prelude::*, Stateful};
|
||||
|
||||
mod ast;
|
||||
|
||||
pub trait Operand {}
|
||||
|
||||
pub trait Visitor<T> {
|
||||
fn visit(&mut self, args: &T, type_: &Type, space: StateSpace, is_dst: bool);
|
||||
}
|
||||
|
||||
pub trait VisitorMut<T> {
|
||||
fn visit(&mut self, args: &mut T, type_: &Type, space: StateSpace, is_dst: bool);
|
||||
}
|
||||
|
||||
pub trait VisitorMap<From, To> {
|
||||
fn visit(&mut self, args: From, type_: &Type, space: StateSpace, is_dst: bool) -> To;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MovDetails {
|
||||
pub typ: Type,
|
||||
pub src_is_address: bool,
|
||||
// two fields below are in use by member moves
|
||||
pub dst_width: u8,
|
||||
pub src_width: u8,
|
||||
// This is in use by auto-generated movs
|
||||
pub relaxed_src2_conv: bool,
|
||||
}
|
||||
|
||||
impl MovDetails {
|
||||
fn new(vector: Option<VectorPrefix>, scalar: ScalarType) -> Self {
|
||||
MovDetails {
|
||||
typ: Type::maybe_vector(vector, scalar),
|
||||
src_is_address: false,
|
||||
dst_width: 0,
|
||||
src_width: 0,
|
||||
relaxed_src2_conv: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gen::generate_instruction_type!(
|
||||
enum Instruction<T> {
|
||||
Mov {
|
||||
type: { &data.typ },
|
||||
data: MovDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src: T
|
||||
}
|
||||
},
|
||||
Ld {
|
||||
type: { &data.typ },
|
||||
data: LdDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src: {
|
||||
repr: T,
|
||||
space: { data.state_space },
|
||||
}
|
||||
}
|
||||
},
|
||||
Add {
|
||||
type: { data.type_().into() },
|
||||
data: ast::ArithDetails,
|
||||
arguments<T>: {
|
||||
dst: T,
|
||||
src1: T,
|
||||
src2: T,
|
||||
}
|
||||
},
|
||||
St {
|
||||
type: { &data.typ },
|
||||
data: StData,
|
||||
arguments<T>: {
|
||||
src1: {
|
||||
repr: T,
|
||||
space: { data.state_space },
|
||||
},
|
||||
src2: T,
|
||||
}
|
||||
},
|
||||
Ret {
|
||||
data: RetData
|
||||
},
|
||||
Trap { }
|
||||
}
|
||||
);
|
||||
|
||||
pub struct LdDetails {
|
||||
pub qualifier: ast::LdStQualifier,
|
||||
pub state_space: StateSpace,
|
||||
pub caching: ast::LdCacheOperator,
|
||||
pub typ: Type,
|
||||
pub non_coherent: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ArithDetails {
|
||||
Unsigned(ScalarType),
|
||||
Signed(ArithSInt),
|
||||
Float(ArithFloat),
|
||||
}
|
||||
|
||||
impl ArithDetails {
|
||||
fn type_(&self) -> ScalarType {
|
||||
match self {
|
||||
ArithDetails::Unsigned(t) => *t,
|
||||
ArithDetails::Signed(arith) => arith.typ,
|
||||
ArithDetails::Float(arith) => arith.typ,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ArithSInt {
|
||||
pub typ: ScalarType,
|
||||
pub saturate: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ArithFloat {
|
||||
pub typ: ScalarType,
|
||||
pub rounding: Option<RoundingMode>,
|
||||
pub flush_to_zero: Option<bool>,
|
||||
pub saturate: bool,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum RoundingMode {
|
||||
NearestEven,
|
||||
Zero,
|
||||
NegativeInf,
|
||||
PositiveInf,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||
pub enum Type {
|
||||
// .param.b32 foo;
|
||||
Scalar(ScalarType),
|
||||
// .param.v2.b32 foo;
|
||||
Vector(ScalarType, u8),
|
||||
// .param.b32 foo[4];
|
||||
Array(ScalarType, Vec<u32>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
fn maybe_vector(vector: Option<VectorPrefix>, scalar: ScalarType) -> Self {
|
||||
match vector {
|
||||
Some(VectorPrefix::V2) => Type::Vector(scalar, 2),
|
||||
Some(VectorPrefix::V4) => Type::Vector(scalar, 4),
|
||||
None => Type::Scalar(scalar),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ScalarType> for Type {
|
||||
fn from(value: ScalarType) -> Self {
|
||||
Type::Scalar(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StData {
|
||||
pub qualifier: ast::LdStQualifier,
|
||||
pub state_space: StateSpace,
|
||||
pub caching: ast::StCacheOperator,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RetData {
|
||||
pub uniform: bool,
|
||||
}
|
||||
pub use ast::*;
|
||||
|
||||
impl From<RawStCacheOperator> for ast::StCacheOperator {
|
||||
fn from(value: RawStCacheOperator) -> Self {
|
||||
|
@ -350,7 +181,7 @@ fn immediate_value<'a, 'input>(stream: &mut PtxParser<'a, 'input>) -> PResult<as
|
|||
|
||||
fn fn_body<'a, 'input>(
|
||||
stream: &mut PtxParser<'a, 'input>,
|
||||
) -> PResult<Vec<Instruction<ParsedOperand<'input>>>> {
|
||||
) -> PResult<Vec<Instruction<ParsedOperandStr<'input>>>> {
|
||||
repeat(3.., terminated(parse_instruction, Token::Semicolon)).parse_next(stream)
|
||||
}
|
||||
|
||||
|
@ -550,7 +381,7 @@ impl<'input, I: Stream<Token = Self> + StreamIsPartial, E: ParserError<I>> Parse
|
|||
// * If it is mandatory then it is skipped
|
||||
// * If it is optional then its type is `bool`
|
||||
|
||||
type ParsedOperand<'input> = ast::ParsedOperand<&'input str>;
|
||||
type ParsedOperandStr<'input> = ast::ParsedOperand<&'input str>;
|
||||
|
||||
derive_parser!(
|
||||
#[derive(Logos, PartialEq, Eq, Debug, Clone, Copy)]
|
||||
|
@ -601,7 +432,7 @@ derive_parser!(
|
|||
// https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#data-movement-and-conversion-instructions-mov
|
||||
mov{.vec}.type d, a => {
|
||||
Instruction::Mov {
|
||||
data: MovDetails::new(vec, type_),
|
||||
data: ast::MovDetails::new(vec, type_),
|
||||
arguments: MovArgs { dst: d, src: a },
|
||||
}
|
||||
}
|
||||
|
@ -622,7 +453,7 @@ derive_parser!(
|
|||
qualifier: weak.unwrap_or(RawLdStQualifier::Weak).into(),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: cop.unwrap_or(RawStCacheOperator::Wb).into(),
|
||||
typ: Type::maybe_vector(vec, type_)
|
||||
typ: ast::Type::maybe_vector(vec, type_)
|
||||
},
|
||||
arguments: StArgs { src1:a, src2:b }
|
||||
}
|
||||
|
@ -633,7 +464,7 @@ derive_parser!(
|
|||
qualifier: volatile.into(),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::StCacheOperator::Writeback,
|
||||
typ: Type::maybe_vector(vec, type_)
|
||||
typ: ast::Type::maybe_vector(vec, type_)
|
||||
},
|
||||
arguments: StArgs { src1:a, src2:b }
|
||||
}
|
||||
|
@ -647,7 +478,7 @@ derive_parser!(
|
|||
qualifier: ast::LdStQualifier::Relaxed(scope),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::StCacheOperator::Writeback,
|
||||
typ: Type::maybe_vector(vec, type_)
|
||||
typ: ast::Type::maybe_vector(vec, type_)
|
||||
},
|
||||
arguments: StArgs { src1:a, src2:b }
|
||||
}
|
||||
|
@ -661,7 +492,7 @@ derive_parser!(
|
|||
qualifier: ast::LdStQualifier::Release(scope),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::StCacheOperator::Writeback,
|
||||
typ: Type::maybe_vector(vec, type_)
|
||||
typ: ast::Type::maybe_vector(vec, type_)
|
||||
},
|
||||
arguments: StArgs { src1:a, src2:b }
|
||||
}
|
||||
|
@ -669,13 +500,13 @@ derive_parser!(
|
|||
st.mmio.relaxed.sys{.global}.type [a], b => {
|
||||
state.push(PtxError::Todo);
|
||||
Instruction::St {
|
||||
data: StData {
|
||||
data: ast::StData {
|
||||
qualifier: ast::LdStQualifier::Relaxed(MemScope::Sys),
|
||||
state_space: global.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::StCacheOperator::Writeback,
|
||||
typ: type_.into()
|
||||
},
|
||||
arguments: StArgs { src1:a, src2:b }
|
||||
arguments: ast::StArgs { src1:a, src2:b }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -704,7 +535,7 @@ derive_parser!(
|
|||
qualifier: weak.unwrap_or(RawLdStQualifier::Weak).into(),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: cop.unwrap_or(RawLdCacheOperator::Ca).into(),
|
||||
typ: Type::maybe_vector(vec, type_),
|
||||
typ: ast::Type::maybe_vector(vec, type_),
|
||||
non_coherent: false
|
||||
},
|
||||
arguments: LdArgs { dst:d, src:a }
|
||||
|
@ -719,7 +550,7 @@ derive_parser!(
|
|||
qualifier: volatile.into(),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::LdCacheOperator::Cached,
|
||||
typ: Type::maybe_vector(vec, type_),
|
||||
typ: ast::Type::maybe_vector(vec, type_),
|
||||
non_coherent: false
|
||||
},
|
||||
arguments: LdArgs { dst:d, src:a }
|
||||
|
@ -734,7 +565,7 @@ derive_parser!(
|
|||
qualifier: ast::LdStQualifier::Relaxed(scope),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::LdCacheOperator::Cached,
|
||||
typ: Type::maybe_vector(vec, type_),
|
||||
typ: ast::Type::maybe_vector(vec, type_),
|
||||
non_coherent: false
|
||||
},
|
||||
arguments: LdArgs { dst:d, src:a }
|
||||
|
@ -749,7 +580,7 @@ derive_parser!(
|
|||
qualifier: ast::LdStQualifier::Acquire(scope),
|
||||
state_space: ss.unwrap_or(StateSpace::Generic),
|
||||
caching: ast::LdCacheOperator::Cached,
|
||||
typ: Type::maybe_vector(vec, type_),
|
||||
typ: ast::Type::maybe_vector(vec, type_),
|
||||
non_coherent: false
|
||||
},
|
||||
arguments: LdArgs { dst:d, src:a }
|
||||
|
@ -931,7 +762,7 @@ fn main() {
|
|||
println!("{}", mem::size_of::<Token>());
|
||||
|
||||
let mut input: &[Token] = &[][..];
|
||||
let x = opt(any::<_, ContextError>.verify_map(|t| {
|
||||
let x = opt(any::<_, ContextError>.verify_map(|_| {
|
||||
println!("MAP");
|
||||
Some(true)
|
||||
}))
|
||||
|
@ -948,13 +779,11 @@ fn main() {
|
|||
);
|
||||
let tokens = lexer.map(|t| t.unwrap()).collect::<Vec<_>>();
|
||||
println!("{:?}", &tokens);
|
||||
let mut stream = PtxParser {
|
||||
let stream = PtxParser {
|
||||
input: &tokens[..],
|
||||
state: Vec::new(),
|
||||
};
|
||||
let fn_body = fn_body.parse(stream).unwrap();
|
||||
println!("{}", fn_body.len());
|
||||
//parse_prefix(&mut lexer);
|
||||
let mut parser = &*tokens;
|
||||
println!("{}", mem::size_of::<Token>());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue