Shop OBEX P1 Docs P2 Docs Learn Events
Parameterized OBJ instantiations — Parallax Forums

Parameterized OBJ instantiations

cgraceycgracey Posts: 14,133
edited 2022-10-18 06:20 in Propeller 2

Right now, OBJ's get set up like this:

OBJ name[count] : "filename"

Would it be nice to be able to pass compile-time parameters which could override CON symbols in the child object, like this?

OBJ name[count] : "filename" x=1, y=2, z=3

This could allow parameterization from the top object all the way down.

Parameterization would be handy for setting things like VAR buffer sizes in child objects.

«1

Comments

  • Would the override also be able to include addpins derived values? That would be handy I think, to save a bunch of pin functions.

    CON myBasePin 0 addpins 7
    OBJ name[count] : "filename" BASEPIN=myBasePin


    filename.spin2
    CON BASEPIN = 16 addpins 7

  • Do multiple copies of the same object share their code? It seems like if they had different CON parameters that they couldn't do that.

    Here is situation that it would help. (from my ntsc decoder) But I won't request this since I have a solution already.

       '' Configure pin number
       adc_pin := cpin
       repeat xi from 0 to 15
          unpack.LONG[xi] :=  (unpack.LONG[xi]) & !(3<<19) | ((cpin&3)<<19)  ' Replace NN bits
    
    
    unpack
                    '' Unpack the samples
                    '' Byte NN is programmed from spin according to the pin selected
                    getbyte samp+0,buf+0,#0
                    getbyte samp+1,buf+1,#0
                    getbyte samp+2,buf+2,#0
                    getbyte samp+3,buf+3,#0
                    ...
    

    The ntsc input program always writes longs from the streamer. That seemed to gave the best performance, and later testing on the forum showed performance loss when writing shorts or bytes. So, selecting which ADC pin requires selecting which byte of the long to operate on. Maybe I could have the streamer write at an offset, but that seems like a messy solution.

  • cgraceycgracey Posts: 14,133
    edited 2022-10-18 07:16

    @VonSzarvas said:
    Would the override also be able to include addpins derived values? That would be handy I think, to save a bunch of pin functions.

    CON myBasePin 0 addpins 7
    OBJ name[count] : "filename" BASEPIN=myBasePin


    filename.spin2
    CON BASEPIN = 16 addpins 7

    OBJ objectname : "filename" basepin = 16 addpins 7

    ...That would override the basepin assignment in the child object.

  • cgraceycgracey Posts: 14,133
    edited 2022-10-18 07:20

    @SaucySoliton said:

    Do multiple copies of the same object share their code? It seems like if they had different CON parameters that they couldn't do that.

    Here is situation that it would help. (from my ntsc decoder) But I won't request this since I have a solution already.

       '' Configure pin number
       adc_pin := cpin
       repeat xi from 0 to 15
          unpack.LONG[xi] :=  (unpack.LONG[xi]) & !(3<<19) | ((cpin&3)<<19)  ' Replace NN bits
    
    
    unpack
                    '' Unpack the samples
                    '' Byte NN is programmed from spin according to the pin selected
                    getbyte samp+0,buf+0,#0
                    getbyte samp+1,buf+1,#0
                    getbyte samp+2,buf+2,#0
                    getbyte samp+3,buf+3,#0
                    ...
    

    The ntsc input program always writes longs from the streamer. That seemed to gave the best performance, and later testing on the forum showed performance loss when writing shorts or bytes. So, selecting which ADC pin requires selecting which byte of the long to operate on. Maybe I could have the streamer write at an offset, but that seems like a messy solution.

    The objects would become unique if their code became different due to the CON overrides. Then there would be two separate instances in memory.

  • Good stuff!

    This almost seems like an alternative to #ifdef types of compile time options. Is that what you're thinking?

    IMO would be even better (at some point) to support ifdefs too, but the CON override has a really useful place, and a pretty clean structure too. I can think of many good uses.

  • This may be an ugly couple of things to bring up, but At this moment, I am writing P1 code that would benefit from this... what about:
    OBJ name[count] : "filename" [0] x=1, y=2, z=3 [1] x=5, y=2, z=7 [and so on]

  • Well, if anything, this should interact with a possible implementation of #ifdef. Switching sections of code is more useful than just changing CON values. Those can often be runtime variables.

  • JonnyMacJonnyMac Posts: 8,923
    edited 2022-10-18 12:48

    I would find that useful with serial and other buffered objects. ATM, I am updating my serial objects so that I pass the RX and TX buffer sizes along with pointers to RX and TX buffers that live in the parent. The reason, of course, is to not be stuck with the fixed size of buffers in the class file. It would be nice to do this...

      term : "jm_fullduplexserial" RX_SIZE=128, TX_SIZE=64
    

    ...and have the instance buffers (byte arrays) sized from that. As someone else pointed out, I could use this on the P1 right now.

    Then there would be two separate instances in memory.

    Does this mean if I have two serial objects with different buffer sizes I will have RAM and code space for each? One of the problems with Parallax Spin compilers is using too much space; will this get worse if I use the same class twice with different constant definitions?

  • cgraceycgracey Posts: 14,133

    @"R Baggett" said:
    This may be an ugly couple of things to bring up, but At this moment, I am writing P1 code that would benefit from this... what about:
    OBJ name[count] : "filename" [0] x=1, y=2, z=3 [1] x=5, y=2, z=7 [and so on]

    OBJ name0 : "filename" x=1, y=2, z=3
    OBJ name1 : "filename" x=4, y=5, z=6
    OBJ name2 : "filename" x=7, y=8, z=9

    Then you could do:

    name0[n].go()

  • cgraceycgracey Posts: 14,133

    @Wuerfel_21 said:
    Well, if anything, this should interact with a possible implementation of #ifdef. Switching sections of code is more useful than just changing CON values. Those can often be runtime variables.

    IFDEF would be something additional.

  • evanhevanh Posts: 15,187
    edited 2022-10-18 12:59

    There is a niggle with #define functionality. It traditionally exists outside of things like CON sections so can't use those very constants. But #define functionality is very handy for configuring what code sections are compiled at all.

    On the other hand, I've been quite surprised how much can be done using CON symbols and the ? : operator pair in compile time data building.

  • @cgracey said:

    @"R Baggett" said:
    This may be an ugly couple of things to bring up, but At this moment, I am writing P1 code that would benefit from this... what about:
    OBJ name[count] : "filename" [0] x=1, y=2, z=3 [1] x=5, y=2, z=7 [and so on]

    OBJ name0 : "filename" x=1, y=2, z=3
    OBJ name1 : "filename" x=4, y=5, z=6
    OBJ name2 : "filename" x=7, y=8, z=9

    Then you could do:

    name0[n].go()

    That makes so much sense.. I want it!

  • You'd want to make sure you are not restricted by some arbitrary line character limit if there are large numbers of parameters needing customization for that feature (or for child objects that have lots of long identifier names etc). Or you could include a technique for line continuation in that case.

    It'd be nice to be able to pass down CON defined values, then you could compute your parameters if this is required, rather than setting them all explicitly on the OBJ lines.

  • I think it would be rather useful to be able to access labels in DAT section like public methods.

    Alike
    subobject:
    DAT
    mySetting long 123

    mainobject:

    OBJ
    obj sub : "filename"
    ...
    mainvar:=sub.mySetting
    or
    sub.mySetting:=456

    Mike

  • Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery

    (from the signature of @"Phil Pilgrim (PhiPi)" )

    Simple things should be simple, complex things should be possible

    (from the "Intuition" manual of the Amiga)

    So please do not make the Spin2 language more complicated than neccessary. What could be done with CON-parametrized OBJs what couldn't by passing parameters to the Start() method?

  • roglohrogloh Posts: 5,158
    edited 2022-10-20 00:11

    @ManAtWork said:
    So please do not make the Spin2 language more complicated than neccessary. What could be done with CON-parametrized OBJs what couldn't by passing parameters to the Start() method?

    Memory buffer dimensioning was the first example of one use Chip had above. Without dynamic memory in the P2 object layout we need static memory allocation unfortunately which can be limiting and requires specific attention. I can see other uses to customize driver building (not spawning), although IMO the #ifdef feature would help there too. If they could be coupled together that could be even handier...with the child object's ifdef values/expressions being definable/overridable by the parent object for example.

    Edit: I think to keep the use of child objects simple it would be good to ensure you don't have to pass anything if you don't want to from the parent and the child would then use its own (default) value of the constant. i.e. make the feature totally optional for ALL child objects, including those that will be able to make good use of it.

  • What if instead of a text preprocessor, we get statements for conditional assembly in DAT sections (and dead branch elimination in PUB/PRI). That'd make everything come together.

    i.e. if/else in DAT sections (would need the end token because no indents)

    DAT
    
    if some_expression > 2
             mov blah,flerp
    else
             scas flerp,#$5000
             neg blah,0-0
    end
    
  • We might be able to use a similar approach to what GNU make uses with our assignments to our constants in child objects... GNU make uses ?= as the operator to assign if the variable is not already assigned. This could let a child object set a default for a constant and you would explictly know which constant identifiers can be overridden by the parent.

    There is another assignment operator for variables, ‘?=’. This is called a conditional variable assignment operator, because it only has an effect if the variable is not yet defined. This statement:

    FOO ?= bar

    is exactly equivalent to this (see The origin Function):

    ifeq ($(origin FOO), undefined)
    FOO = bar
    endif

  • @ManAtWork said:

    Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away. -Antoine de Saint-Exupery

    (from the signature of @"Phil Pilgrim (PhiPi)" )

    Simple things should be simple, complex things should be possible

    (from the "Intuition" manual of the Amiga)

    So please do not make the Spin2 language more complicated than neccessary. What could be done with CON-parametrized OBJs what couldn't by passing parameters to the Start() method?

    Agree 100%.

    In this case, If it is not needed, it adds no complexity. You can do what you want without using this, but it can simplify your code:

    I wish I could do this, where I really need the Propeller to just act like multiple chips with no overhead logic:
    OBJ
    Nest : "Nestlogic" Startpin = 0, Newcog = 1
    Nest1: "Nestlogic" Startpin = 20, Newcog = 1
    Nest2: "Nestlogic" Startpin = 4, Newcog = 1
    Nest3: "Nestlogic" Startpin = 16, Newcog = 0

    'Each object statement says it all. In 3 years, I will still understand this at a glance. I won't need to keep the constants straight while passing them in because there they are..

    Then:
    PUB Start |idx
    repeat idx from 0 to 3
    NestLogic[idx].go

    Yes, I can (And have) do(ne) this without this feature, passing the data in via the go(s,c) and it will still be simple, but this would make the top object so clear, so short..

    What if I needed to call the Nest[] from multiple places instead of just starting it? this encapsulation would cut down on on what might be lengthy parameterization.

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2022-10-20 20:33

    The only reason I can see for doing something like this is to override object constants (e.g. buffer lengths) during pre-compilation. Everything else can be handled in a start method. Of course this entails multiple copies of the same object that have different constant settings.

    -Phil

  • cgraceycgracey Posts: 14,133

    @"Phil Pilgrim (PhiPi)" said:
    The only reason I can see for doing something like this is to override object constants (e.g. buffer lengths) during pre-compilation. Everything else can be handled in a start method. Of course this entails multiple copies of the same object that have different constant settings.

    -Phil

    Yes, it has the sad effect of creating near-identical copies of the same object. How this has been handled in the past was to have the parent declare any large buffer of unknown size and pass a pointer and size info down to the child object. This was a little messier, but resulted in smaller code in the case of there being more than one instance of a child object.

    Where this would definitely save memory would be in cases of a single instances of a child object where the I/O pins could be hard-coded. This would get around having to make variables containing pin numbers for specific pins.

    Adding this parameterization would be useful in some cases, but would encourage writing objects which are not totally flexible in most cases.

  • This would get around having to make variables containing pin numbers for specific pins.

    I've never considered that to be a problem. That's what start methods are for.

    -Phil

  • My case for preprocessing/conditional assembly is stuff like this snippet (from NeoYume's memory arbiter cog).

    ma_do_adpcm
    
    #ifdef USE_PSRAM_EITHER
                  shl     ma_mtmp1,#MA_PCM_ASHIFT ' ADPCM cache lines are 4 longs
    #ifndef USE_PSRAM_NOBANKS
                  mov ma_mtmp3,ma_mtmp1
                  shr ma_mtmp3,#MA_BANKSHIFT
                  fle ma_mtmp3,#PSRAM_BANKS-1
    #endif
                  setbyte ma_mtmp1,#$EB,#3
                  splitb  ma_mtmp1
                  rev     ma_mtmp1
                  movbyts ma_mtmp1, #%%0123
                  mergeb  ma_mtmp1
                  rep @.irqshield,#1
                  drvl  ma_psram_pinfield
    #ifdef USE_PSRAM_SLOW
                  drvl  #PSRAM_CLK ' must be 2 ops before xinit
    #ifdef USE_PSRAM_NOBANKS
                  nop
    #endif
    #endif
    #ifndef USE_PSRAM_NOBANKS
                  altd  ma_mtmp3,#PSRAM_SELECT
    #endif
                  drvh  #PSRAM_SELECT
                  xinit ma_psram_addr_cmd,ma_mtmp1
                  wypin #(8+PSRAM_WAIT+MA_PCM_CYCLES)*MA_CYMUL,#PSRAM_CLK
                  setq ma_nco_fast
                  xcont #PSRAM_WAIT*MA_CLKDIV+PSRAM_DELAY,#0
                  waitxmt
                  fltl ma_psram_pinfield
                  mov ma_mtmp3,ma_adpcm_pollptr
                  sub ma_mtmp3,#@adpcm_pollbox
                  shl ma_mtmp3,#3 ' 4*8 -> 32 bytes per channel
                  testb ma_mtmp1,#(MA_PCM_ASHIFT^28) wc ' odd blocks go in odd buffers (bit 0 of original poll value)
            if_c  add ma_mtmp3,#16
                  add ma_mtmp3,ma_adpcm_bufferbase
                  wrfast ma_bit31,ma_mtmp3
                  setq ma_nco_slow
                  xcont ma_psram_readpcm_cmd,#0
                  waitxfi
                  drvl #PSRAM_SELECT addpins (PSRAM_BANKS-1)
    #ifdef USE_PSRAM_SLOW
                  fltl  #PSRAM_CLK
    #endif
    .irqshield
    #elseifdef USE_HYPER
                  ' 16 byte ADPCM blocks line up with HyperRAM half-pages
                  setbyte ma_mtmp1,#%101_00000,#3 ' read linear burst
                  movbyts ma_mtmp1, #%%0123
                  rep @.irqshield,#1
                  drvh #HYPER_SELECT
                  drvl #HYPER_BASE addpins 7
                  drvl #HYPER_CLK ' Init clock pin with correct (?) alignment
                  xinit ma_hyper_addr_cmd1,ma_mtmp1
                  wypin #(6+HYPER_WAIT+MA_PCM_CYCLES)*MA_CYMUL,#HYPER_CLK ' setup clock periods
                  xcont ma_hyper_addr_cmd2,#0 ' no intra-page address needed
                  setq ma_nco_fast
                  xcont #HYPER_WAIT*MA_CLKDIV+HYPER_DELAY,#0
                  waitxmt
                  fltl #HYPER_BASE addpins 7
                  mov ma_mtmp3,ma_adpcm_pollptr
                  sub ma_mtmp3,#@adpcm_pollbox
                  shl ma_mtmp3,#3 ' 4*8 -> 32 bytes per channel
                  testb ma_mtmp1,#(MA_PCM_ASHIFT^24) wc ' odd blocks go in odd buffers (bit 0 of original poll value)
            if_c  add ma_mtmp3,#16
                  add ma_mtmp3,ma_adpcm_bufferbase
                  wrfast ma_bit31,ma_mtmp3
                  setq ma_nco_slow
                  xcont ma_hyper_readpcm_cmd,#0
                  waitxfi
                  drvl #HYPER_SELECT addpins (HYPER_BANKS-1)
                  fltl #HYPER_CLK
    .irqshield
    #endif
            _ret_ wrlong #0,ma_adpcm_pollptr
    

    Note that all the constants (such as MA_PCM_ASHIFT) are either dependent on memory type, but that could be (horribly!) faked with ternary operators (could we have constant expression lookupz?).

    Setting something like this up entirely through a runtime start(...) call is of course possible, just really, really obnoxious. Especially since anything you'd want to reference needs to have a global label, so byebye local ones. (I think flexspin actually can do something like ma_do_adpcm.irqshield to refer to a local label? not sure).

    This code doesn't even deal with memory writes, which are far more complex...

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2022-10-21 03:18

    IMO, programs laden with #ifdefs and others of that ilk are virtually unreadable. The reason is that you constantly have to scroll to the top of a program to see which definitions are in force before you can tell which program sections will be compiled. And the omitted sections are totally distracting from the flow of comprehension.

    But there's nothing wrong with having a preprocessor that's a separate program and that produces discrete, preprocessed source-code files, unburdened by #ifdefs, that a human can look at, understand, and that can be fed to a compiler.

    I just don't want a compiler that also does preprocessor work, since it hides any results of the preprocessing stage. IOW, I want to see what's actually being compiled.

    -Phil

  • @"Phil Pilgrim (PhiPi)" said:
    But there's nothing wrong with having a preprocessor that's a separate program and that produces discrete, preprocessed source-code files, unburdened by #ifdefs, that a human can look at, understand, and that can be fed to a compiler.

    I do understand that point whenever #ifdef is really overdone and nested in large projects as I've suffered wading through that myself however the main problem with going with the separate tool approach is that it makes software requiring any conditional compilation much harder to distribute in source form as it requires additional tools installed in order to even build the code which beginners won't necessarily have present or even understand etc. How does CPP work in Windows for example? Do you need to install MinGW etc? Then the challenge becomes supporting installation of different tools on different platform and educating the users on running Makefiles or build scripts etc and I expect it rapidly becomes a turn off for them. Much handier to have at least some aspects of conditional compilation features built into the widely used toolchains like PropTool and FlexSpin to avoid that overhead.

    I do still feel we sort of need some type of built in capability for conditional compilation to reduce code duplication but it probably doesn't have to be the entire preprocessor shebang. Even using existing consts that can be used with some new type of conditional compilation syntax in SPIN could potentially suffice. But I guess that is separate to the original discussion Chip started with OBJ enhancement proposal.

  • @cgracey said:

    @"Phil Pilgrim (PhiPi)" said:
    The only reason I can see for doing something like this is to override object constants (e.g. buffer lengths) during pre-compilation. Everything else can be handled in a start method. Of course this entails multiple copies of the same object that have different constant settings.

    -Phil

    Yes, it has the sad effect of creating near-identical copies of the same object. How this has been handled in the past was to have the parent declare any large buffer of unknown size and pass a pointer and size info down to the child object. This was a little messier, but resulted in smaller code in the case of there being more than one instance of a child object.

    Where this would definitely save memory would be in cases of a single instances of a child object where the I/O pins could be hard-coded. This would get around having to make variables containing pin numbers for specific pins.

    Adding this parameterization would be useful in some cases, but would encourage writing objects which are not totally flexible in most cases.

    In my example, I could have done what I'm doing with 4 small PICs. The Propeller is just so much easier to load and use (This is on a FLIP BTW). The code size is not a concern in this case. If this were a sophisticated job, where every long counts, it wouldn't make sense to use this, but in this stupid simple application, the encapsulation would be very welcome.

    If it makes sense to use this feature (If added) then use it. If it doesn't make sense, don't use it,
    *********************I see no penalty to this feature being available. ******************************
    (Or preprocessor/conditional assembly for that matter, If it makes sense, use it, but no one will put a gun to your head and insist that you do..)

  • @rogloh said:

    @"Phil Pilgrim (PhiPi)" said:
    But there's nothing wrong with having a preprocessor that's a separate program and that produces discrete, preprocessed source-code files, unburdened by #ifdefs, that a human can look at, understand, and that can be fed to a compiler.

    The main problem with going with the separate tool approach is that it makes software requiring any conditional compilation much harder to distribute in source form as it requires additional tools installed in order to even build the code which beginners won't necessarily have present or even understand etc. How does CPP work in Windows for example? Do you need to install MinGW etc? Then the challenge becomes supporting installation of different tools on different platform and educating the users on running Makefiles or build scripts etc and I expect it rapidly becomes a turn off for them. Much handier to have at least some aspects of conditional compilation features built into the widely used toolchains like PropTool and FlexSpin to avoid that overhead.

    ^ This.

    The readability issue can be solved at the editor level. See VSC's C plugin:

    (ignore the fact that it is too stupid to notice that WIN32 is defined by the compiler. I'd need to set that somewhere in a config file.)

  • Kind of funny to read that @cgracey is confused by the readability of ifdef and company.

    Skip pattern are way more complicated to follow.

    And a seperate precompiler would not be such a mess if the same one could be used and installed by Proptool and Flexspin.

    Mike

  • RaymanRayman Posts: 13,860

    I like the idea of being able to override base pin settings from top level

  • evanhevanh Posts: 15,187

    I have yet to use a skip pattern. I guess it's because I've not run out of cogRAM in my experimenting so not needed to compact any code.

Sign In or Register to comment.