-
Notifications
You must be signed in to change notification settings - Fork 98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AST-89 Range restricted Nat* and Int* #487
Conversation
and a `Nat` bugfix
- backend: all done -interpreter: `->Nat*` and `->Int*` mostly missing
word32ToNat32 3000000000; crashes word64ToNat64 300000000000000000; probably crashes
and other roundtrip tests
add range tests
and fix msb_adjust for Int/Nat -> IntN/NatN
still needs Int/Nat8/64!
caveat: when using libTomMath binding these fail: - intToInt8 (-128) - intToInt16 (-32768) -intToInt32 (-2147483648) The reason is that `fits_signed_bits` is buggy. (BigNum64 module works in this respect.)
relaxing the fits_signed_bits contract to work with 64
393e979
to
15f798b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM but I haven't reviewed the compiler - leaving that to Joachim.
PS. Part of me wants to redefine the fixed-size numeric literals so we just have three constructors (WordX, IntX and NatX) that take a module giving the interpretation of the type as an argument and a value of that type.
Something that might be expressed in Haskell as:
WordX : forall w :: Word w, w.
NumX: forwall w:: Num w. w.
NumBinOp: forall n :: Numeric n. nbop * exp * exp
LogBinOp: forall n :: Numeric n. lbop * exp * exp
NumUnOp: forall n :: Numeric n. nup * exp
LogNumOp: forall n :: Logical n. lup * exp
And then the interpeter/compiler would use the first-class module/dictionary to interpret/compile accordingly...
But maybe that would be less direct than what we have now.
@@ -720,7 +733,8 @@ let interpret_prog flags scope p : (V.value * scope) option = | |||
let vo = ref None in | |||
let ve = ref V.Env.empty in | |||
Scheduler.queue (fun () -> | |||
interpret_block env p.it (Some ve) (fun v -> vo := Some v) | |||
try interpret_block env p.it (Some ve) (fun v -> vo := Some v) | |||
with Invalid_argument s -> trap !last_region "%s" s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you still need try … with ...
now that you transform Invalid_argument
to trap at the interpretation of operations?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only catch some. I could not make catching where CallE
is interpreted to work. (It would have broken general tail-recursion.) So this is still needed. In some ideal world we'll have failure continuations where we can feed the exception into, instead of raise
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the remaining places that raise Invalid_argument? Can't you just handle those, as you do for binop and immediately trap? CallE shouldn't (by itself) trap at all. If you think CallE should turn the failure of v_asfunc into a trap I think that is mistake since that an indicates a type soundness bug, not an expected trap. Or perhaps I'm misunderstanding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, its probably not worth worrying about since the scheduler itself doesn't use tailcalls to evaluate its tasks:
module Scheduler =
struct
let q : (unit -> unit) Queue.t = Queue.create ()
let queue work = Queue.add work q
let yield () =
trace_depth := 0;
try Queue.take q () with Trap (at, msg) ->
Printf.printf "%s: execution error, %s\n" (Source.string_of_region at) msg
let rec run () =
if not (Queue.is_empty q) then (yield (); run ())
end
That would need fixing first (separately from this), for your try with
to make any difference.
Sorry about the distraction...
|
||
let prim = function | ||
| "abs" -> fun v k -> k (Int (Nat.abs (as_int v))) | ||
|
||
| "Nat8->Word8" -> fun v k -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there's enough uniformity in all these conversions that you could refactor them into a uses of a few single higher order function that takes Numeric modules are arguments. Probably not worth it now you've completed but maybe as a future refactoring...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd need some sparring on how to use first-class modules, but yeah I was thinking of that. Unfortunately the primitives are not totally homogenous (some details differ). Also the NumType
and WordType
modules don't have all the methods that we need for interpretation and compilation, so a new functor would be needed. We should re-eval this cleanup idea for v1.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine - also, it's not like were going to add more fixed width types in the foreseeable future...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Meta NIT: first-order -> first-class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course. Fixed comment above.
|
||
include Rep | ||
let neg a = let res = Rep.neg a in check res | ||
let abs a = let res = Rep.abs a in check res |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you've implemented all the binary interpretations in value.ml, I wonder if we should just implement the binary operators for AS - or is there much more work in the compiler to do this?
Again, as a separate PR, I'd think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be mostly straight-forward, as most arithmetic (8-32 width) can be performed in 64 bits and checked for overflow. pow
and mul
are trickier, but I know how to check for a fast-path.
I prefer to hand this work over to the IDL community, so they can integrate it, while (me) working on the backend operations in parallel.
@@ -411,7 +448,7 @@ and infer_exp'' env exp : T.typ = | |||
| UnE (ot, op, exp1) -> | |||
let t1 = infer_exp_promote env exp1 in | |||
(* Special case for subtyping *) | |||
let t = if t1 = T.Prim T.Nat then T.Prim T.Int else t1 in | |||
let t = special_unop_typing t1 in | |||
if not env.pre then begin | |||
assert (!ot = Type.Pre); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be very tempted to changed the syntax type checker to annotate BinE and UnE with the partially evaluated interpretations of the operators, so we don't have to repeat dispatch every time we call an operator... Whadday think? Again, this would be as a separate PR...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if I like code-in-the-data here. Would cause problems for pretty-printing, or serialization/deserialization. Also, smells a bit of premature optimization. Show me profiling data before you argue about performance :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nomeata Not sure I buy that. The pretty printer can ignore it, serializer and deserialized drop and recompute from the type stored in ot. And I really don't think I need a profiler to tell me that doing redundant work (think of interpreted arithmetic in a loop) is a bad idea.
Plus, it would be more true to the intention that operators are resolved statically, not at runtime. Indeed, weren't you arguing for this in the past? But true, none of that should be part of this PR....
interpret_exp env exp1 (fun v1 -> k (Operator.unop !ot op v1)) | ||
interpret_exp env exp1 | ||
(fun v1 -> | ||
k (try Operator.unop !ot op v1 with Invalid_argument s -> trap exp.at "%s" s)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For that separate PR I alluded to, it's a shame that we repeatedly resolve operators at application. We could instead store the resolved operation once as an additional field in ot during type-checking and just directly use it during interpretation... In fact, if resolution took a regions as an argument you also partially evaluate the source location too, removing the need for the try with
here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume, by "resolution" you mean the worker Operator.unop
? Then yes, passing the srcloc to it makes a lot of sense.
let call_conv, f = V.as_func v1 in | ||
check_call_conv exp1 call_conv; | ||
check_call_conv_arg env exp v2 call_conv; | ||
last_region := exp.at; (* in case the following throws *) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see why your are object now, since we don't even have a stack of regions, you need to reset the region here. Yuck, but I still think it's ok for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EPARSE
:-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Err, I don't get the reference...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see why your are object now
My completion list for this:
- "I see why you are objecting now"
- "I see your objective now"
both don't really make sense to me in the context. Maybe you can explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I meant "I see why you are objecting now, ..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just looked at compile.ml
@crusso you requested changes, but I am not sure I can distill what exactly. I guess rewrite |
Well, I wanted you to remove the try with if possible (and wait for Joachim's review). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm approving (pending Joachim's approval).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM as well
I don't think allowing the non-canonical range in `Word*` patterns is a good idea, so I have not enabled them in `desugar.ml`.
@@ -5132,6 +5132,48 @@ and compile_lit_pat env l = | |||
| (NatLit _ | IntLit _) -> | |||
compile_lit_as env SR.Vanilla l ^^ | |||
BigNum.compile_eq env | |||
| Nat8Lit _ -> | |||
snd (compile_lit env l) ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see no point spelling out the SR
for compile_lit_as
all the time, instead I rely on what is done by both lines below implicitly. Also there is no guarantee that the latter operates on Vanilla
(other than inspecting the implementation).
BoxedWord.unbox env ^^ | ||
snd (compile_lit env l) ^^ | ||
compile_eq env Type.(Prim Int64) | ||
| Word8Lit _ -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Words were an earlier omission, filled in now.
snd (compile_lit env l) ^^ | ||
compile_eq env Type.(Prim Int16) | ||
| Int32Lit _ -> | ||
BoxedSmallWord.unbox env ^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the scrutinee is captured as SR.Vanilla
we need to unbox it for *32
and *64
prim types.
and other review feedback
I am convinced now that they are not needed. For the 8/16/32 bit types the OCaml `int32` provides the correct shift semantics and conversion from `int`. `int` OTOH has enough bits to represent the occurring ranges, negatives including.
https://dfinity.atlassian.net/browse/AST-89
Conversions work. Tests prove it.
There might be better ways of conversion at the 64-bit plane than going via string, but I am not aware of any.I addedMaybe we should addto/from_big_int
methods to theNumType
signature.Open Issue: Since we don't have representational equivalence (subtyping) between
Nat8
andInt8
, we can't do the- : Nat8 -> Int8
hack, so we have to annotate like this:-(42 : Int8)
to get the effect. Thereiswas an attempt in the code to fix this, but itiswas wrong as-is, andwill behas been removedunless we find a better way (e.g. automatic insertion of range checks). Same issue applies to binary operators.==
, etcLaundry list:
-
), need new hack intyping.ml
:- : NatN -> IntN
needs revert -- Done-9_223_372_036_854_775_808 : Int64
givestype error, literal out of range for type Int64
, needs the sign hack (actually, not.) See open issue above.- intToInt64 (- 2 ** 63 + 1)
traps in the wasm, but not in the interpreter (libTomMath
, or binding bug?, fixed by @nomeata and incorporated in b4caf31)abs
,neg
corner cases (where it needs to trap)debug_show
forWord*
,Nat*
andInt*
diff test/run-dfinity/ok/data-params.dvm-run.ok test/run-dfinity/ok/data-params.run-low.ok
(this is now lsb_adjust correct bits when externalising Word8 #502)Nat*
,Int*
andWord*
patterns